How to template'ize variable NAMES, not types?

Viewed 11345

my question is about how to template'ize the name of a class member that should be used.

Maybe a simplified & pseudo example:

/** 
Does something with a specified member of every element in a List.
*/
template<membername MEMBER> // <-- How to define such thing?
void doSomething(std::vector<MyClass> all){

    for( i=0; i < all.size(); i++)
      all[i].MEMBER++; // e.g.; use all[i].MEMBER in same way

}

and

class MyClass{
public:
    int aaa, bbb, ccc;
}

and the application:

main(){
    vector<MyClass> all = ....

    // applicate doSomething() to all aaa's
    doSomething<aaa>(all);  // or:
    doSomething<MyClass::aaa>(all); // or:
    doSomething<?????>(all);
}

How should the template definition looks like, that I can switch which member variable (aaa, bbb or ccc) of MyClass is accessed/modified in doSomething(.) ?
In my real world task all MEMBER are of same type, as above.

Thanks, Tebas

4 Answers

I realize this question is a bit old, but none of the answers use the method I have developed, and I would like to share it.

First, in C++ we typically are discouraged from directly accessing member variables and encouraged to provide setters/getters to help enforce hiding of information.

Second, while C++ goes a long way towards eliminating use of macros, they can still accomplish a lot of things that are difficult (or near impossible) with templates and classes.

The following uses a macro to create typed setters & getters for fields in a container member within a class:

//
// Bit(n) -- sets 'n'th bit.
//    Bit(0) == 0x1 (b0000001),
//    Bit(1) == 0x2 (b0000010),
//    Bit(2) == 0x4 (b0000100),
//    Bit(3) == 0x8 (b0001000), etc.
//
#define Bit(n)        (1 << (n))

//
// BitMask(n) -- creates mask consisting of 'n' bits.
//    BitMask(0) == 0x0 (b00000000),
//    BitMask(1) == 0x1 (b00000001),
//    BitMask(2) == 0x3 (b00000011),
//    BitMask(3) == 0x7 (b00000111), etc.
//
#define BitMask(n)    (Bit(n) - 1)

//
// BitRange(n, m) -- creates mask consisting of bits between n & m, inclusive.
//    BitRange(0, 3) == 0x0f (b00001111),
//    BitRange(2, 5) == 0x3c (b00111100),
//    BitRange(6, 1) == 0x7e (b01111110), etc.
//    
//
#define BitRange(n,m) (BitMask(n) ^ BitMask(m))

#define namedBitField(name, container, start, end, EnumType)                                      \
                              EnumType name() const                                               \
                                {return                                                           \
                                  (EnumType)                                                      \
                                    ((container & BitRange(start,end))                            \
                                      >> start);                                                  \
                                };                                                                \
                              void     name(EnumType v) {container |= (v << start);};             \

class myTest
{
  public:
    enum vSet1
    {
      a = 1,
      b = 2,
    };
  private:
    unsigned long holder;
  public:
    myTest() {};
    namedBitField(set1, holder, 0, 3, vSet1);
    namedBitField(set2, holder, 4, 5, vSet1);
};

myTest  mt;

The namedBitField() macro takes the name for the getter/setter pair, the target container -- holder in this example, the bitfield start/end, and the EnumType that is to be used for values in the bitfield.

If I now use the setter/getter pairs named set1() & set2() in the above example, and attempt to pass POD (plain-old-data) numbers I will get a warning from the compiler.

mt.set1(22); // compiler warns here.
mt.set1();

mt.set2(myTest::vSet1::a); // no warnings.
mt.set2();

No, it is not a "typed bitfield", but it is the next best thing.

No, it is not quite as easy to use as defining bitfields in a struct, but this way you get strong typing via the setters/getters.

Now, you could define the bitfields in structs, make them private, and access them via setters/getters as well, but then the information about where the bits are located is separated from the setters/getters which logically are tied to that information, and as several responders above have pointed out, each C++ compiler can put the bits anywhere they want, so without looking at generated assembler -- or testing on hardware if you are brave -- you cannot be certain things are happening the way you want.

The way the setters/getters created by namedBitField() manipulate the bits in a well-defined order and guarantee bit-order within container, so you can now use the code cross-platform for accessing I/O registers.

Note: in my example I use 'name' as both setter and getter with compiler sorting it out based on use. Some may prefer 'get_name' and 'set_name'. YMMV.

Since the getters/setters are public, and as long as the things you are iterating all derive from the same base class, you can now iterate across the items in a vector -- as above -- and get type-safe getting/setting for the values used in the iteration.

Related