【问题标题】:Resolving Definitions of Specialized Static Member Variables of Templated Classes解析模板类的专用静态成员变量的定义
【发布时间】:2015-08-31 22:43:07
【问题描述】:

Compiler Fights XIV: Doom of the Duplicity Double Definition,由 The Dubious Declaration 共同主演!

编译器,都具有-O0 或调试模式:

  • g++ 5.2.0
  • clang++ 3.6.0
  • VC++ 18.00.40629(MSVC 2013,更新 5)

总结:

  • VC++ 拒绝使用语法的模板类的专用静态成员变量的声明和定义是错误的吗?
template <> const std::string TemplatedClass::x; // .h file
template <> const std::string TemplatedClass::x= "string"; // .cpp file
  • 删除头文件中的声明是否会导致原本定义良好的程序格式错误?
  • 如果是这样,是否有一种 VC++ 友好的方式来声明模板化类的静态成员变量的特化?

在对定义模板的专用静态成员变量时遇到的问题进行 MCVE 时,我遇到了 VC++、GCC 和 Clang 之间关于专用静态成员变量声明的有趣变化。具体来说,语法

template <> const std::string TemplatedClass<int>::x; // .h file
template <> const std::string TemplatedClass<int>::x= "string"; // .cpp file        

似乎严重冒犯了 VC++,它以多种定义的抱怨作为回应:

error C2374: 'member' : redefinition; multiple initialization

gcc 和 clang 都从容应对。

研究

我假设后两者是正确的,因为它们通常是正确的,还因为上述语法来自 answer regarding static member initialization of a specialized template class,它引用了 2010 年标准的第 14.7.3/15 段,指出 template&lt;&gt; X Q&lt;int&gt;::x 是声明,而不是定义。我冒昧地追查了 N4296 草案的相应段落,认为它可能在此期间发生了变化。它有,但只是它向上移动了两段并包含额外的说明:

14.7.3/13

如果声明包含初始化器,则模板的静态数据成员的显式特化或静态数据成员模板的显式特化是定义;否则,它是一个声明。 [注意:需要默认初始化的模板的静态数据成员的定义必须使用braced-init-list:

template<> X Q<int>::x;      // declaration
template<> X Q<int>::x ();   // error: declares a function
template<> X Q<int>::x { };  // definition

—结束注释]

这对我来说似乎很清楚,但 VC++ 似乎有不同的解释。我试过简单地注释掉有问题的声明,没有编译器抱怨,这似乎解决了我的麻烦,但不是因为第 6 段有这样说:(担心我的重点)

14.7.3/6

如果模板、成员模板或类模板的成员被显式特化,则该特化应在第一次使用该特化之前声明,这将导致隐式实例化发生,在每个翻译单元中发生使用; 不需要诊断。如果程序没有为显式特化提供定义,并且特化的使用方式会导致隐式实例化或成员是虚拟成员函数, 程序格式错误,不需要诊断。 永远不会为已声明但未定义的显式特化生成隐式实例化。

它提供了示例,但所有这些示例都用于在使用后专门化函数或专门化模板类型的成员枚举和类,我相当肯定这不适用于这个问题。但是,p13 的开头词似乎暗示了专门化静态成员变量的声明也是一种明确的专门化,至少在使用图示的语法时是这样。

MCVE

我用于实验的测试可以在 Coliru 上找到,对于相当复杂的命令行向 StackedCrooked 表示歉意。一个大大缩短的版本如下:

main.cpp

#include <iostream>

// 'header' file
#include "test.h"

int main(){

  std::cout << test::FruitNames<Fruit::APPLE>::lowercaseName();

}

test.h(声明未注释掉)
test.h(声明注释掉)

#ifndef TEMPLATE_TEST
#define TEMPLATE_TEST

#include <algorithm>
#include <locale>
#include <string>

namespace test{

  enum class Fruits{
    APPLE
  };

  template <Fruits FruitType_>
  class FruitNames{
    static const std::string name_;

  /*...*/

  public:
    static std::string lowercaseName() {/*...uses name_...*/}
  };

    // should be counted as declaration. VC++ doesn't.
  template <> const std::string FruitNames<Fruits::APPLE>::name_;

} // end namespace test

#endif // TEMPLATE_TEST

test.cpp

#include "test.h"

namespace test{

  template <> const std::string FruitNames<Fruits::APPLE>::name_ = "Apple";

}

输出

gcc 和 clang 都会输出

apple

在 test.h 中有或没有特殊化声明。如果 test.h 中的声明被注释掉,VC++ 会这样做,但如果存在,则会产生双重初始化错误。

终于

  • 如前所述,VC++ 拒绝模板类的静态成员变量的声明/显式特化语法是不正确的,还是允许但不是强制性的诊断错误?
  • 删除声明是否会导致程序 格式不正确?
  • 如果在没有声明的情况下格式不正确,我如何让 VC++ 与 定义明确的程序?

【问题讨论】:

  • 第一个代码sn-p是不是要省略TemplatedClass之后的模板参数?

标签: c++ templates language-lawyer


【解决方案1】:

VC++ 如前所述拒绝模板类的静态成员变量的声明/显式特化语法是不正确的,还是允许但非强制性的诊断错误?

是的,这是a bug in VC++。它显然已在 Visual Studio 2019 版本 16.5 Preview 2 中得到修复。


删除声明是否会导致程序格式错误?

您对标准的引用似乎表明了这一点。 Other people agree.


如果在没有声明的情况下格式不正确,我如何让 VC++ 与定义良好的程序配合使用?

作为一种解决方法,您可以专门化整个类,然后在不使用template&lt;&gt; 语法的情况下定义成员。请参阅 Amir Kirsh 对类似问题的回答: https://stackoverflow.com/a/58583521/758345

或者,您可以在标题中定义和初始化变量并将其标记为内联 (since c++17):

template <> inline const std::string TemplatedClass::x = "string"; // .h file

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    相关资源
    最近更新 更多