如何将变量名称模板化,而不是类型?

17

我的问题是如何将应该使用的类成员的名称进行模版化。

也许这里有一个简化的伪例子:

/** 
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

}

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

并且应用程序:

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

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

我应该如何定义模板才能切换访问/修改MyClass的哪个成员变量(aaa, bbb或ccc)在doSomething(.)中?
在我的实际任务中,所有MEMBER都是相同类型的,就像上面一样。

谢谢, Tebas

5个回答

25

模板参数仅限于类型、整数常量、具有外部链接的函数或对象的指针/引用以及成员指针,但不包括标识符。

但是你可以使用成员指针作为模板参数:

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

:

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

请注意,我已经将 doSomething 函数更改为使用引用而不是按值接受向量。


谢谢。'按值调用'是为了简化问题...但是成员指针模板需要'按引用调用'吗?还是我也可以使用副本? - Tebas
@Tebas:你可以使用拷贝,但这意味着doSomething的效果在函数外部不可见。通过引用调用是为了确保结果在调用者的向量中可见。 - Bart van Ingen Schenau
@Tebas:不,这没有任何区别。在任何情况下,all[i]都将是类型为MyClass的lvalue表达式。 - sellibitze
很遗憾,不能有指向引用类型数据成员的成员指针,也不能使用成员指针来捕获一组重载函数或函数模板 :( 最近Usenet上有人提出了“名称”模板参数,可以实现这一点。我喜欢这个建议。 - Johannes Schaub - litb

10

sellibitze的解决方案是可以的(虽然说实话不太好:请看我的编辑),但它限制了你只能使用int类型的成员。更通用的解决方案是这样的(虽然这里成员不是一个模板参数)

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

编辑: 还请注意,将成员指针作为模板参数通常不是一个好主意,因为只有已知编译时的指针才能被传递,也就是说你不能在运行时确定指针然后将其作为模板参数传递。


vector::all() 返回的是 size_t 而不是 unsigned - 请在 i 中使用 size_t。(使用迭代器会更好一些…) - Steve M
1
@Steve:我完全同意迭代器的问题,我只是从OP的代码中复制了文本。我不会改变它。size()函数并不返回size_t类型,而是返回std::vector<MyClass>::size_type类型。那么vector::all是什么? :D - Armen Tsirunyan
哎呀,你在第一点上是正确的,而我在第二点上想得有些混乱。给你加1分。奇怪的是,我刚刚回答了一个关于向量的问题,并知道要使用size_type而不是size_t。有时候我真的很糟糕。 - Steve M

1
我会使用lambda来解决这个问题。类似这样:
#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(); });
}

当然,在这种情况下,函数doSomething是不必要的。我可以直接在all上调用for_each

0

我知道这个问题有点老了,但是我开发了一种方法,没有任何答案使用它,我想分享一下。

首先,在C++中,我们通常不鼓励直接访问成员变量,而是鼓励提供设置器/获取器来帮助实现信息隐藏。

其次,虽然C++在消除宏的使用方面已经取得了很大进展,但宏仍然可以完成许多使用模板和类难以实现(或几乎不可能)的事情。

以下使用宏为类中的容器成员字段创建类型化的设置器和获取器:

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

namedBitField() 宏接受 getter/setter 对的 name,目标container -- 在此示例中为 holder,位域的 start/end,以及用于位域值的 EnumType

如果我现在在上面的示例中使用名为 set1()set2() 的 setter/getter 对,并尝试传递 POD(plain-old-data)数字,则会收到编译器的警告。

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

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

不,它不是一个“类型化位域”,但它是次优选择。

不,使用定义结构中的位域并不像这种方式那么容易,但是通过setter/getter您可以获得强类型。

现在,您可以在结构体中定义位域,将它们设置为私有,并通过setter/getter访问它们,但这样会导致有关位的位置的信息与用于该信息的setter/getter分离。正如上面的几位回答者所指出的那样,每个C++编译器都可以将位放置在任何位置,因此如果不查看生成的汇编代码或在硬件上进行测试(如果你很勇敢),你不能确定事情是否按照你想要的方式发生。

namedBitField()创建的setter/getter以一定顺序操纵位,并保证container内的位顺序,因此您现在可以跨平台使用代码来访问I/O寄存器。

注意:在我的示例中,我将“名称”用作setter和getter,编译器根据使用情况自动排序。有些人可能更喜欢“get_name”和“set_name”。您的情况可能会有所不同。

由于getter/setter是公共的,只要你迭代的所有东西都派生自同一个基类,你现在可以遍历向量中的项目(如上所述),并获得用于迭代中使用的值的类型安全的获取/设置。


几乎所有这些都可以使用函数和模板来完成,只需要一个宏来创建最终的一对函数。 - aschepler
我不太确定你在这个评论中想表达什么。Bit...()宏只是用来展示位操作的。只有一个宏可以创建函数。 - kch_PE_MSEE_BSCE

0
使用一个特征类型类。与std::function不同,它在编译时操作,并且不限制您正在获取的数据。
#include <iostream>

class TheClass
{
public:
    int SOME_FIELD;
};

template<typename TClass, typename TTrait>
class TheTemplate
{
public:
    TClass Value;
    
    int WhatField()
    {
        return TTrait::GetField(Value);
    }
};

class TheTrait
{
public:
    static int GetField(TheClass& f)
    {
        return f.SOME_FIELD;
    }
};


int main() {
    TheTemplate<TheClass, TheTrait> theTemplate{TheClass{5}};
    std::cout << theTemplate.WhatField();
    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接