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

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

  • g 5.2.0

  • clang 3.6.0

  • VC 18.00.40629(MSVC 2013,Update 5)

Summary:

  • 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<> X Q<int>::x 是一个声明,而不是一个定义 . 我冒昧地追查了N4296草案的同等段落,认为它可能在此期间发生了变化 . 它有,但只是因为它移动了两段,并包含额外的说明:

14.7.3/13

如果声明包含初始化程序,则模板的静态数据成员的显式特化或静态数据成员模板的显式特化是定义;否则,这是一个声明 . [注意:需要默认初始化的模板的静态数据成员的定义必须使用braced-init-list:template <> X Q <int> :: x; // 宣言
template <> X Q <int> :: x(); //错误:声明一个函数
template <> X Q <int> :: x {}; //定义

  • 结束说明]

这对我来说似乎很清楚,但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与定义良好的程序一起玩得很好?