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.

10
  • 3
    Why do you need the separated members when you have vector already? Can you get rid of variables themselves? Commented Aug 28, 2013 at 9:07
  • @Nawaz because it is preferable to retain the usual member variable syntax, i.e. not having to say *addrs[0] = 5; Commented Aug 28, 2013 at 9: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? Commented Aug 28, 2013 at 9: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<int>. Commented Aug 28, 2013 at 9:16
  • @Hulk I will probably use a map, but the same issue still applies. Commented Aug 28, 2013 at 9:16

4 Answers 4

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
Sign up to request clarification or add additional context in comments.

1 Comment

Excellent, exactly solves the problem, and useful example even if I go for a redesign.
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>(); }
};

1 Comment

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.
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;
}

Comments

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;
};

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.