【问题标题】:Inline member initializer containing pointer to member包含指向成员的指针的内联成员初始化程序
【发布时间】:2015-04-01 13:34:43
【问题描述】:

在工作中,我正在尝试将一些反思带入我们的代码库。基本上我想要实现的是在数据成员的初始化程序类型中捕获指向数据成员的指针:

template<class Class, int Class::*dataMember>
struct Reflect
{
  operator int() {return 0;}
};

class Foo
{
public:
  int bar = Reflect<Foo, &Foo::bar>{};
};

虽然 clang 3.4.1 (http://gcc.godbolt.org/) 和 Intel C++ XE 14.0 能够编译这段代码,但在使用 MSVC12 时,我收到以下错误消息:

错误 C2065:'bar':未声明的标识符

错误 C2975:“dataMember”:“Reflect”的模板参数无效,预期的编译时常量表达式

此外,gcc 4.9.2 似乎也有问题:http://ideone.com/ZUVOMO

所以我的问题是:

  1. 上面这段代码是有效的 C++11 吗?
  2. 如果是,是否有针对失败编译器的解决方法?

【问题讨论】:

    标签: c++ templates c++11 initializer pointer-to-member


    【解决方案1】:

    VC++ 抱怨的当然不是问题; [basic.scope.pdecl]/1,6:

    名称的声明点在其完成后立即 声明符(第 8 条)在它的初始化器之前(如果有的话),除了 如下所述。[…]

    在类成员声明点之后,成员名可以 在其类的范围内查找。

    这意味着名称查找没问题。然而,正如@hvd 在 cmets 中指出的那样,这种结构的语法存在一定的歧义。
    大概 GCC 会解析上面的行,直到逗号:

    int bar = Reflect<Foo,
    // at this point Reflect < Foo can be a perfectly fine relational-expression.
    // stuff after the comma could be a declarator for a second member.
    

    一旦遇到其他问题就会退出。


    让 GCC 满意的解决方法是
        int bar = decltype( Reflect<Foo, &Foo::bar>{} )();
    

    Demo。但是,这对 VC++ 没有帮助,这显然混淆了错误消息所指示的声明点。 因此将初始化程序移入构造函数将起作用:

    int bar;
    
    Foo() : bar( Reflect<Foo, &Foo::bar>{} ) {}
    // (also works for GCC)
    

    ...在bar 的声明中提供初始化程序时不能。 Demo #2 在 rextester 上。

    【讨论】:

    • 这是一个未解决的语言问题,IIRC。目前还不清楚逗号何时分隔模板参数,何时分隔两个成员。 int a=b&lt;c,d=e&gt;::f; 可以声明成员a 并将其初始化为b&lt;c,d=e&gt;::f,或者可以声明成员a 初始化为b&lt;c,并将成员d 初始化为e&gt;::f。 NSDMI 可能引用尚未声明的类成员这一事实使情况变得复杂。
    • @hvd 我认为语法明确地涵盖了这一点。好点子。你能找到与此相关的 DR 吗?
    • 对于与函数默认参数非常相关的问题,有#325。也许这就是我所记得的,也许 NSDMI 没有单独的问题。
    • @hvd 我很确定d=e 不能是有效的模板参数。但是,这是从解析器开始的语法问题,不是吗?`
    • 一个重载的赋值运算符可以声明为constexpr,所以赋值可以发生在模板参数中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-13
    • 1970-01-01
    • 1970-01-01
    • 2013-05-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多