在类范围内声明时,应在 C++14 中引用变量模板

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

本文关键字:引用 变量 C++14 应在 范围内 声明      更新时间:2023-10-16

例如:

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 段。

类 X 的静态成员 s 可以使用

限定 id 表达式 X::s 来引用;不必使用类成员访问语法 (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是必需的:类范围内的变量模板

注意 3:由于我想初始化变量模板,因此还需要关键字constexpr,因为在我的实际代码中,我将使用浮点数、双精度和长双精度数实例化它(请参阅 C++14 标准 (ISO/IEC 14882:2014),第 9.4.2 节,第 3 段)。

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

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

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

std::cout << example::var<int> << "an";

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

std::cout << obj.var<int> << "bn";

现在这就是它变得有趣的地方...不仅上面的行没有打印出obj.var<int>的值,'b'n也永远不会被打印出来。我甚至针对std::coutgood()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

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

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

不必

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

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

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

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

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

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

tl;博士

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