【问题标题】:How should a Variable Template be referred to in C++14 when declared at Class scope?在 Class 范围内声明时,应如何在 C++14 中引用变量模板?
【发布时间】:2017-05-06 22:04:38
【问题描述】:

例如:

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&lt;T&gt;' 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) 来引用静态成员。可以使用类成员访问语法来引用静态成员,在这种情况下,对象表达式会被求值。

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

我发现试图证明这种行为的唯一方法是第 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标准(ISO/IEC 14882:2014),第 9.4.2 节,第 3 段)。

注意 4:在本示例情况下,类外部的这些静态数据成员(即template &lt;class T&gt; constexpr T example::var;)的实际“定义”是不需要的。不过我也试过了,但没什么区别。

【问题讨论】:

  • 您使用的是哪个版本的 gcc? g++ 6.2 的行为就像铿锵声。但与 clang 不同的是,obj.var&lt;int&gt; 的错误消息是 var 不是成员模板函数”。从 g++ 5 开始,第一部分似乎已经修复。
  • 我正在使用 gcc 5.2.1 和 clang 3.7.0 :)
  • Clang trunk 接受此代码。
  • 只是说,第 9.4.2 节,第 1 段与此无关,它是关于对象布局而不是访问语法
  • 感谢@OlegBogdanov 的评论,我不完全确定该特定句子适用于什么:)

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


【解决方案1】:

我将您的第一个代码复制到 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&lt;int&gt; 的值,'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 的非常量静态成员,您可以编写example_object.something = 42,不希望在整个程序中对该类的所有其他实例进行任何更改(与全局变量相同的问题,真的)。因此(而且成员访问语法显然不适用于模板静态成员变量这一事实),我建议始终使用范围解析从类外部访问/修改静态内容。 example_class::something = 42 更清楚地表明我们正在为example_class所有 个实例更改something。事实上,一些更现代的语言(如 C#)要求您通过类名访问静态数据,除非您在该类中。

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

tl;dr

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

【讨论】:

  • 确保使用std::flushstd::endl 刷新您的流。这或许可以解释为什么您在 std::cout &lt;&lt; obj.var&lt;int&gt; &lt;&lt; "b\n"; 上看不到任何内容。
  • @FrançoisAndrieux 好吧,我确实说过我后来也使用了流(成功了,但我想我忘了提到它也被显示了),所以缓冲区中的任何内容都应该显示用那些东西
  • 感谢@DeMayo 花时间检查这个问题。您的分析很有趣,并且证实了我的想法(您也说过):标准和编译器都没有很好地涵盖“新”C++14 变量模板。事实上,标准委员会中仍然存在一些关于变量模板的活动核心语言问题:open-std.org/jtc1/sc22/wg21/docs/cwg_active.html 我也同意您关于使用范围解析访问静态数据的建议,我只是在测试标准和编译器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-18
  • 2017-02-12
  • 2017-08-19
  • 2020-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多