16

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

Tebas
  • 547
  • 1
  • 6
  • 13

4 Answers4

23

Template parameters are restricted to types, integer constants, pointers/references to functions or objects with external linkage and member pointers -- but no identifiers.

But you could use a member pointer as template parameter:

template<int MyClass::* MemPtr>
void doSomething(std::vector<MyClass> & all) {
   for( i=0; i < all.size(); i++)
      (all[i].*MemPtr)++;
}

:

doSomething<&MyClass::aaa>(all);

Note that I changed the doSomething function to take a reference instead of accepting the vector by value.

sellibitze
  • 27,611
  • 3
  • 75
  • 95
  • Thanks. Call 'all' by value was for simplification of the question ... but is call by reference requested by member pointer templates? or can i also use a copy? – Tebas Oct 15 '10 at 13:23
  • @Tebas: You can use a copy, but that means the effect of `doSomething` will not be visible outside the function. Call by reference was used to ensure the result would be visible in the caller's vector. – Bart van Ingen Schenau Oct 15 '10 at 13:45
  • @Tebas: No, it doesn't make any difference. In either case all[i] will be an lvalue expression of type MyClass. – sellibitze Oct 15 '10 at 13:45
  • Too bad that there can't be a member pointer to a data-member of reference type, and that member pointers can't be used to catch a set of overloaded functions or a function template :( Someone on usenet lately proposed "name" template parameters that would make this possible. I liked the proposal. – Johannes Schaub - litb Oct 15 '10 at 20:39
10

sellibitze's solution is fine (though to be honest not very: see my edit), only it limits you to using only members of type int. A more general solution would be this (although the member is NOT a template parameter here)

#include <vector>

struct MyClass
{
   int i;
   char c;
};

template <class T>
void DoSomething(std::vector<MyClass>& all, T MyClass::* MemPtr)
{ 
   for(std::vector<MyClass>::size_type i = 0; i < all.size(); ++i)
      (all[i].*MemPtr)++;
}

int main()
{
   std::vector<MyClass> all;
   DoSomething(all, &MyClass::i);
   DoSomething(all, &MyClass::c);
}

EDIT: Also please note that it is not generally a good idea for a pointer to member to be a template parameter inasmuch as only such pointers that are known compile-time can be passed, that is you can't determine the pointer runtime and then pass it as a template param.

Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
  • `vector::all()` returns `size_t` and not `unsigned` - please use `size_t` for `i`. (Iterators would be even better…) – Steve M Oct 15 '10 at 14:38
  • 1
    @Steve: I totally agree about the iterators, I just copied the text from op's code. I won't change that. and size() does NOT return size_t, it returns std::vector::size_type. And what is vector::all? :D – Armen Tsirunyan Oct 15 '10 at 14:47
  • Oops, you are correct on point 1 and I don't know what I was thinking on point 2. +1 for you. Oddly enough, I *just answered* a question related to vectors and knew to use `size_type` and not `size_t`. Sometimes I am bad. – Steve M Oct 15 '10 at 15:16
1

I would use lambdas to solve this problem. Something like this:

#include <vector>     // vector
#include <algorithm>  // for_each
#include <functional> // function

struct MyClass {
   void func1() const { std::cout << __FUNCTION__ << std::endl; }
   void func2() const { std::cout << __FUNCTION__ << std::endl; }
};

void doSomething(std::vector<MyClass> all, std::function<void (MyClass& m)> f)
{
   std::for_each(all.begin(), all.end(), f);
}

int main()
{
   std::vector<MyClass> all;
   all.push_back(MyClass());

    // apply various methods to each MyClass:
   doSomething(all, [](MyClass& m) { m.func1(); });
   doSomething(all, [](MyClass& m) { m.func2(); });
}

Of course in this case the function doSomething is unnecessary. I could just as simply call for_each directly on all.

Daniel Lidström
  • 9,930
  • 1
  • 27
  • 35
0

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.

  • Almost all of this could be done with functions and templates as well, with just one macro to create a final pair of functions. – aschepler Mar 20 '20 at 22:52
  • I'm not really sure where you are going with this comment. The Bit...() macros are just there to show the bit manipulation. There is "just one macro" which creates the functions. – kch_PE_MSEE_BSCE Mar 20 '20 at 23:12