在Class范围内声明时,如何在C ++ 14中引用变量模板?

时间:2016-12-21 13:07:24

标签: c++ templates c++14 static-members variable-templates

例如:

class example{
    public:
        template <class T> static constexpr T var = T(1.5);
};

int main(){

    int a = example::var<int>;

    example obj;
    int b = obj.var<int>;

    return 0;
}

GCC为两者产生错误: 'example::var<T>' is not a function template'var' is not a member template function

Clang正确编译第一个,但第二个产生错误:cannot refer to member 'var' in 'example' with '.'

根据C ++ 14标准(ISO / IEC 14882:2014):

第14节,第1段。

  

类范围内的变量模板是静态数据成员模板。

第9.4节,第2段。

  

可以使用qualified-id表达式X :: s引用类X的静态成员;没有必要使用类成员访问语法(5.2.5)来引用静态成员。可以使用类成员访问语法引用静态成员,在这种情况下,将评估对象表达式。

因此,可以以两种方式引用恕我直言,类范围的变量模板(即,静态数据成员模板)。它可能是编译器中的错误吗?

我发现试图证明这种行为的唯一理由是第9.4.2节第1段中的这句话:

  

静态数据成员不是类的子对象的一部分。

但是,前面提到的两个段落仍然有效。此外,我尝试了同样的例子,引用其他静态成员,如变量,函数和函数模板,所有这些成员都在GCC和Clang中成功编译。

class example{
    public:
        static int constexpr variable = 1;
        void static function(){ return; }
        template <class T> void static function_template(){ return; }
};

int main(){

    example obj;

    int a = obj.variable;
    int b = example::variable;

    obj.function();
    example::function();

    obj.function_template<int>();
    example::function_template<int>();

   return 0;
}

先谢谢。

注1:编译器版本为clang 3.7.0和gcc 5.2.1。

注意2:关键字static是必需的:Variable template at class scope

注3:因为我想初始化变量模板,所以也需要关键字constexpr,因为在我的实际代码中,我将使用float,double和long double实例化它(参见C ++ 14 Standard(ISO) / IEC 14882:2014),第9.4.2节,第3段。

注4:在此示例情况下,不需要类外的这些静态数据成员的实际“定义”(即template <class T> constexpr T example::var;)。我也试过了,但没有区别。

1 个答案:

答案 0 :(得分:2)

我将您的第一个代码复制到Visual Studio 2015中(它已成功编译)。我通过std::cout添加了一些输出,我发现使用b会产生编译错误:uninitialized local variable 'b' used。另一方面,a在未使用b时成功打印。所以看起来c ++对于访问模板静态成员有点挑剔,如你所说,要求你通过其完整的限定名称来引用它。

也许更好奇的是以下几行:

std::cout << example::var<int> << "a\n";

上述行按预期工作,输出1.5截断为1'a'输出新行。什么都没有写回家。

std::cout << obj.var<int> << "b\n";

现在这里有趣的地方......上面的行不仅没有打印出obj.var<int>的值,而且'b'\n也不会被打印出来。我甚至针对std::cout的{​​{1}} good()fail()函数进行了测试,其中没有一个函数报告任何错误(并且bad()的进一步使用已被执行成功输出)。

我发现的另一个奇怪之处是std::cout是合法的,并且发现,x的类型为auto x = obj.var。现在,使用全局模板变量执行此操作会导致编译器错误(正如我预期的那样第一个):

example

此外,我发现通过另一个模板静态函数访问template<typename T> constexpr T ex = 1.5; auto x = ex // compiler error: argument list for variable template "ex" is missing 是成功的,这进一步暗示成员选择在这种情况下不起作用

var

现在,就标准而言,我倾向于相信他们的措辞有点......迂腐。您从标准中引用的部分:

  

没有必要使用类成员访问语法(5.2.5)来引用静态成员。

  

类范围内的变量模板是静态数据成员模板。

似乎暗示这会起作用。我相信这些引用在这种情况下不适用的点是技术性,即模板(任何东西)在编译单元中实例化之前并不真正“存在”(即为什么模板的代码通常包含在头文件本身)。

因此,虽然模板变量可以是类的成员,但是它的实例化不是......由于某种原因......因此需要范围解析运算符而不是成员选择运算符。

但IMO通过成员选择运算符访问静态数据是不好的做法,因为静态数据和函数实际上不是给定对象的一部分。以与非静态数据相同的方式访问静态数据会导致相对无辜的代码实际上是有缺陷的逻辑。例如,如果由于某种原因你有一个名为class example { public: template <class T> static constexpr T var = T(1.5); template <typename T> static void thing() { std::cout << var<T> << '\n'; // works std::cout << example::var<T> << '\n'; // also works } }; 的非const静态成员,你可以编写something,而不期望在整个程序中为该类的所有其他实例更改任何内容(与全局变量,真的)。由于这一点(以及成员访问语法显然不适用于模板静态成员变量的事实),我建议始终使用范围解析来访问/修改类外部的静态内容。 example_object.something = 42更加明确我们正在为example_class::something = 42所有实例更改something。事实上,一些更现代的语言,如C#,要求通过类名访问静态数据,除非你在所说的类中。

鉴于有几个编译器在这个小例子程序的不同部分出错,我愿意打赌它在标准中没有很好地覆盖(并且在实践中可能不经常使用),并且编译器只是处理它不同(避免它的另一个原因)。

<强> TL;博士

显然,当成员选择语法适用于静态成员变量时,它不适用于模板静态成员变量(尽管编译器似乎没有抱怨)。但是,范围解析语法 可以正常工作,无论如何都应该首选IMO。