2

I would like to simplify the following

class A {
    int a;
    int b;
    int c;
    std::vector<int*> addrs;
public:
    A() : addrs{ &a, &b, &c } {}
};

so that I don't have the write the list of fields in two places, i.e. the declarations and the initializer for addrs. Is there some way to use a macro to collect the declarations and use them later. E.g.,

class A {
    VAR_DECL(a);
    VAR_DECL(b);
    VAR_DECL(c);
    std::vector<int*> addrs;
public:
    A() : addrs{ VAR_ADDRESSES } {}
};

For context this is intended for implementing some kind of property introspection system.

jarmond
  • 1,372
  • 1
  • 11
  • 19
  • 3
    Why do you need the separated members when you have vector already? Can you get rid of variables themselves? – Nawaz Aug 28 '13 at 09:07
  • @Nawaz because it is preferable to retain the usual member variable syntax, i.e. not having to say *addrs[0] = 5; – jarmond Aug 28 '13 at 09:13
  • It sounds like what you want to create is some kind of map of property names to their values. If so, why did you decide not to use a map? – Hulk Aug 28 '13 at 09:14
  • @Nawaz although I suppose one could declare the member variables to be references into the vector..., int& a and then initialize as a(vals[0]) where vals is the vector. – jarmond Aug 28 '13 at 09:16
  • @Hulk I will probably use a map, but the same issue still applies. – jarmond Aug 28 '13 at 09:16
  • Hm guess I see - you want member syntax for ease of use, but also to be able to iterate over members (e.g. for serialization purposes?) – Hulk Aug 28 '13 at 09:19
  • As currently defined this is bad. As the copy constructor/assignment operator will break your code (the copy will have an array of pointers into the original object). I would rather have the vector hold the values. Then make `a`, `b` and `c` be references into the array. With a reference the compiler generated versions of copy constructor/assignment operator will not work. – Martin York Aug 28 '13 at 09:21
  • @LokiAstari good point, I think will try using holding the values in a container. – jarmond Aug 28 '13 at 09:25
  • @jarmond If Hulk is correct and you want to iterate over members, why not implement some kind of member visitor. A `ForEachMember` method taking a functor as argument. Advantages: Default copy and move constructors still work (unlike your example), members may be of different types. – Sebastian Aug 28 '13 at 10:04
  • You are storing statically available information in heap memory. That's simpy wasteful – Sebastian Aug 28 '13 at 10:06

4 Answers4

6

You could do it using Boost Preprocessor.

#define VARIABLES (a)(b)(c)

#define DECLARE_MEMBER(maR, maType, maId) \
  maType maId;

#define TAKE_ADDRESS(maR, maUnused, maIndex, maId) \
  BOOST_PP_COMMA_IF(maIndex) & maId

class A {
  BOOST_PP_SEQ_FOR_EACH(DECLARE_MEMBER, int, VARIABLES)
  std::vector<int*> addrs;
public:
  A() : addrs { BOOST_PP_SEQ_FOR_EACH_I(TAKE_ADDRESS, %%, VARIABLES) } {}
};

// Now you can clean up:
#undef DECLARE_MEMBER
#undef TAKE_ADDRESS
// In case you no longer need the list, also:
#undef VARIABLES
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
2

I usually refrain from "Don't do this, you really want to do that instead" answers. But in this case the problem is too obvious.

  1. You are allocating memory on the heap for information that is available at compile time. That is horrible.

  2. Your implementation unnecessarily breaks the default copy and move constructor behavior. I hope you are aware of that. I hope everyone reusing that code is aware of that.

  3. I guess what you are trying to achieve is a generic way to visit all your members. Do something like the following:

    class A {
        int a;
        int b;
        int c; 
    
    public:
        A() {}
    
        template<class F> ForEachMember(F f) {
            f(a);
            f(b);
            f(c);
        }
    };
    

This supports members of different type if the F::operator() is overloaded.

If that is a frequent pattern through your code, and you think repeating the member names is error-prone, you could use boost::tuple and boost::fusion:

#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/adapted/boost_tuple.hpp>
#include <boost/fusion/include/boost_tuple.hpp>

class A : boost::tuple<int, int, int> {
    template<class F> ForEachMember(F f) {
       boost::fusion::for_each( *this, f );
    }

    // if necessary, write getter/setter with pretty names
    int& a() { return get<0>(); }
};
Sebastian
  • 4,802
  • 23
  • 48
  • You still have to write a, b and c (or however many there might be) in two places and keep them in sync. That's mainly what I was hoping to avoid. – jarmond Aug 28 '13 at 13:04
0

You could eliminate the address vector and iterate over the members (I kept that vector here, though)

#include <iostream>
#include <tuple>
#include <vector>

// Dedicated function

template <typename T, std::size_t I = 0, typename ...Tuple>
inline typename std::enable_if<I == sizeof...(Tuple), void>::type
collect_addresses(std::vector<T*>& result, std::tuple<Tuple...>&) {
}

template <typename T, std::size_t I = 0, typename ...Tuple>
inline typename std::enable_if<I < sizeof...(Tuple), void>::type
collect_addresses(std::vector<T*>& result, std::tuple<Tuple...>& tuple) {
    result.push_back(&std::get<I>(tuple));
    collect_addresses<T, I + 1, Tuple...>(result, tuple);
}

template <typename T, typename ...Tuple>
inline std::vector<T*> collect_addresses(std::tuple<Tuple...>& tuple) {
    std::vector<T*> result;
    result.reserve(sizeof...(Tuple));
    collect_addresses(result, tuple);
    return result;
}


// Static function [Tuple]

template <typename Function, std::size_t I = 0, typename ...Tuple>
inline typename std::enable_if<I == sizeof...(Tuple), void>::type
invoke_tuple(const Function&, std::tuple<Tuple...>&) {
}

template <typename Function, std::size_t I = 0, typename ...Tuple>
inline typename std::enable_if<I < sizeof...(Tuple), void>::type
invoke_tuple(const Function& function, std::tuple<Tuple...>& tuple) {
    function(std::get<I>(tuple));
    invoke_tuple<Function, I + 1, Tuple...>(function, tuple);
}


// Member function [Tuple]

template <typename Instance, typename Function, std::size_t I = 0, typename ...Tuple>
inline typename std::enable_if<I == sizeof...(Tuple), void>::type
invoke_tuple(Instance&, const Function&, std::tuple<Tuple...>&) {
}

template <typename Instance, typename Function, std::size_t I = 0, typename ...Tuple>
inline typename std::enable_if<I < sizeof...(Tuple), void>::type
invoke_tuple(Instance& instance, const Function& function, std::tuple<Tuple...>& tuple) {
    (instance.*function)(std::get<I>(tuple));
    invoke_tuple<Instance, Function, I + 1, Tuple...>(instance, function, tuple);
}



// Static function [Variadic Template]

template <typename Function>
inline void invoke(const Function&) {
}

template <typename Function, typename T, typename ...Args>
inline void invoke(const Function& function, T& value, Args&... args) {
    function(value);
    invoke(function, args...);
}


// Member function [Variadic Template]

template <typename Instance, typename Function>
inline void invoke(Instance&, const Function&) {
}

template <typename Instance, typename Function, typename T, typename ...Args>
inline void invoke(Instance& instance, const Function& function, T& value, Args&... args) {
    (instance.*function)(value);
    invoke(instance, function, args...);
}



class A {
    // public in this test
    public:
    std::tuple<int, int, int> params;
    std::vector<int*> addrs;
    A() : addrs(collect_addresses<int>(params))
    {}
};

class B {
    private:
    typedef std::tuple<int, int, int> Params;

    // public in this test
    public:
    Params params;
    std::vector<int*> addrs;
    B()
    {
        addrs.reserve(std::tuple_size<Params>::value);
        invoke_tuple([this](int& i) { addrs.push_back(&i); }, params);
    }
};

class C {
    // public in this test
    public:
    int a;
    int b;
    int c;
    std::vector<int*> addrs;
    C()
    {
        addrs.reserve(3);
        invoke([this](int& i) { addrs.push_back(&i); }, a, b, c);
    }
};

int main(){
    A a;
    for(int* p: a.addrs) std::cout << (const void*)p << std::endl;
    B b;
    for(int* p: b.addrs) std::cout << (const void*)p << std::endl;
    C c;
    for(int* p: c.addrs) std::cout << (const void*)p << std::endl;
}
0

You may use union :

class A {
    A() {
        static_assert(&u.a == &u.vars[0], "&u.a == &u.vars[0] failed");
        static_assert(&u.b == &u.vars[1], "&u.b == &u.vars[1] failed");
        static_assert(&u.c == &u.vars[2], "&u.c == &u.vars[2] failed");
    }
private:
    union {
        struct {
            int a;
            int b;
            int c;
        };
        int vars[3];
    } u;
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302