解析模板类专用静态成员变量的定义

jag*_*ire 6 c++ templates language-lawyer

编译器打架十四:双重双重定义的毁灭,共同主演的可疑宣言!

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

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

摘要:

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

虽然我在定义模板的专用静态成员变量时出现了问题的MCVE,但我在VC++,GCC和Clang之间遇到了一个有趣的变化,就声明所说的专用静态成员变量而言.具体来说,语法

template <> const std::string TemplatedClass<int>::x; // .h file
template <> const std::string TemplatedClass<int>::x= "string"; // .cpp file        
Run Code Online (Sandbox Code Playgroud)

似乎有点冒犯VC++,它回应了多个定义的抱怨:

error C2374: 'member' : redefinition; multiple initialization
Run Code Online (Sandbox Code Playgroud)

虽然gcc和clang都大踏步前进.

研究

我假设后两个是正确的,因为它们通常是,并且因为上面的语法来自关于专门模板类的静态成员初始化答案,它引用了2010年标准中的段落14.7.3/15,并指出template<> X Q<int>::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
Run Code Online (Sandbox Code Playgroud)

- 结束说明]

这对我来说似乎很清楚,但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();

}
Run Code Online (Sandbox Code Playgroud)

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

TEST.CPP

#include "test.h"

namespace test{

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

}
Run Code Online (Sandbox Code Playgroud)

产量

gcc和clang都会输出

apple
Run Code Online (Sandbox Code Playgroud)

有没有test.h中的专业化声明.如果test.h中的声明被注释掉,VC++将这样做,但是如果它存在则会产生双初始化错误.

最后

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

Chr*_*ial 4

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

是的,这是VC++ 中的一个错误。它显然已在Visual Studio 2019 版本 16.5 Preview 2中修复。


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

您对标准的引用似乎表明了这一点。其他人也同意


如果没有声明就格式不正确,我如何让 VC++ 与定义良好的程序很好地配合?

作为解决方法,您可以专门化整个类,然后定义不带语法的成员template<>。请参阅 Amir Kirsh 对类似问题的回答: https: //stackoverflow.com/a/58583521/758345

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

template <> inline const std::string TemplatedClass::x = "string"; // .h file
Run Code Online (Sandbox Code Playgroud)