How to template'ize variable NAMES, not types?

前端 未结 4 1577
Happy的楠姐
Happy的楠姐 2020-12-29 08:19

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

Maybe a simplified & pseudo example:

相关标签:
4条回答
  • 2020-12-29 08:45

    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.

    0 讨论(0)
  • 2020-12-29 08:46

    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.

    0 讨论(0)
  • 2020-12-29 08:50

    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.

    0 讨论(0)
  • 2020-12-29 08:52

    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.

    0 讨论(0)
提交回复
热议问题