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

Jos*_*uis 17 c++ templates static-members variable-templates c++14

例如:

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;
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

提前致谢.

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

注2:关键字static是必需的:类范围的变量模板

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

注4:template <class T> constexpr T example::var;在这个例子中,不需要类外的这些静态数据成员的实际"定义"(即).我也试过,但没有区别.

DeM*_*ayo 2

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

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

std::cout << example::var<int> << "a\n";
Run Code Online (Sandbox Code Playgroud)

上面的行按预期工作,输出1.5被截断1'a'带有新行。没什么可写的。

std::cout << obj.var<int> << "b\n";
Run Code Online (Sandbox Code Playgroud)

现在,事情变得有趣了......上面的行不仅没有打印出 的值obj.var<int>,而且'b'\n也永远不会打印出来。我什至对 和 函数进行了测试std::cout,没有一个报告有任何错误(并且 的进一步使用good() fail()已成功执行输出)。bad()std::cout

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

template<typename T> constexpr T ex = 1.5;
auto x = ex // compiler error: argument list for variable template "ex" is missing
Run Code Online (Sandbox Code Playgroud)

此外,我发现var通过另一个模板静态函数的访问是成功的,进一步暗示成员选择在这种情况下不起作用

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
    }
};
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

但在我看来,通过成员选择运算符访问静态数据是一种不好的做法,因为静态数据和函数实际上并不是给定对象的一部分。以与非静态数据相同的方式访问静态数据可能会导致看起来相对无害的代码实际上是有缺陷的逻辑。例如,如果由于某种原因您有一个名为 的非常量静态成员something,您可以编写example_object.something = 42,而不期望整个程序中该类的所有其他实例发生任何更改(实际上与全局变量相同的问题)。因此(事实上,成员访问语法显然不适用于模板静态成员变量),我建议始终使用范围解析从类外部访问/修改静态内容。example_class::something = 42更清楚的是,我们正在something对的所有实例进行更改example_class。事实上,一些更现代的语言(例如 C#)要求您通过类名访问静态数据,除非您位于该类内部。

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

太长了;博士

显然,虽然成员选择语法适用于静态成员变量,但它不适用于模板静态成员变量(尽管编译器似乎没有抱怨)。然而,作用域解析语法确实有效,而且 IMO 无论如何应该是首选。