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

时间:2015-08-31 22:43:08

标签: c++ templates language-lawyer

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

编译器,都具有-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
  • 删除头文件中的声明会导致定义明确的程序格式不正确吗?
  • 如果是这样,是否有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,表明{{3} 1}}是一个声明,而不是一个定义。我冒昧地追查N4296草案的等效段落,认为它可能在此间隔时间发生变化。它有,但只是因为它移动了两个段落,并包含额外的澄清:

14.7.3 / 13

  

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

template<> X Q<int>::x
     

- 结束说明]

这对我来说非常清楚,但VC ++似乎有不同的解释。我试图简单地评论一下违规的声明,没有编制者抱怨,这似乎可以解决我的麻烦,但不是因为第6段有这样说:(担心强调我的)

14.7.3 / 6

  

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

它提供了一些示例,但它们都是用于在使用它们之后专门化函数或专门化成员枚举和模板类型的类,我相当确定它不适用于此问题。但是,p13的初始词似乎暗示专门的静态成员变量的声明也是一个显式的特化,至少在使用图示的语法时。

MCVE

我在实验中使用的测试可以在Coliru上找到,对于相当复杂的命令行道歉StackedCrooked。缩短版本如下:

main.cpp

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

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

#include <iostream>

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

int main(){

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

}

test.cpp

#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

输出

gcc和clang都会输出

#include "test.h"

namespace test{

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

}

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

最后

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

1 个答案:

答案 0 :(得分:3)

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

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


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

您从标准中引用的内容似乎暗示了这一点。 Other people agree


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

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

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

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