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

How should a Variable Template be referred to in C++14 when declared at Class scope?

例如:

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 款

A variable template at class scope is a static data member template.

第 9.4 节第 2 段

A static member s of class X may be referred to using the qualified-id expression X::s; it is not necessary to use the class member access syntax (5.2.5) to refer to a static member. A static member may be referred to using the class member access syntax, in which case the object expression is evaluated.

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

我发现唯一试图为这种行为辩护的是第 9.4.2 节第 1 段中的这句话:

A static data member is not part of the subobjects of a class.

不过,上述两段仍然有效。此外,我尝试了引用其他静态成员(例如变量、函数和函数模板)的相同示例,并且它们在 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标准(ISO/IEC 14882:2014),第 9.4.2 节,第 3 段)。

注意 4:在本例中不需要 class(即 template <class T> constexpr T example::var;)之外的这些静态数据成员的实际 "definitions"。我也试过了,不过没啥区别。

我将你的第一个代码复制到 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::coutgood() fail()bad() 函数进行了测试,其中 none 报告任何错误(以及 [=14 的进一步使用=]执行成功输出)。

我发现的另一个奇怪的地方是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

此外,我发现通过另一个模板静态函数访问 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
    }
};

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

it is not necessary to use the class member access syntax (5.2.5) to refer to a static member.

A variable template at class scope is a static data member template.

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

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

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

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

tl;dr

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