解析模板化类的特化静态成员变量的定义
Resolving Definitions of Specialized Static Member Variables of Templated Classes
编译器之战十四:双重定义的毁灭,联合主演《可疑宣言》!
编译器,所有的-O0
或Debug模式:
- 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++, vc++对多重定义的抱怨回应:
error C2374: 'member' : redefinition; multiple initialization
,而GCC和clang都可以从容应对。
研究我假设后两个是正确的,因为它们通常是正确的,而且因为上面的语法来自一个关于专用模板类的静态成员初始化的答案,它引用了2010年标准的14.7.3/15段,指出template<> X Q<int>::x
是声明,而不是定义。我冒昧地找到了N4296号草案中相同的段落,认为它可能在这段时间里发生了变化。确实如此,但只是将两段文字往前移了两段,并包含了额外的说明:
14.7.3/13
模板的静态数据成员的显式特化,或者静态数据成员模板的显式特化,如果声明包含初始化式,则是定义;否则,它是一个声明。[注意:需要默认初始化的模板的静态数据成员的定义必须使用大括号-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++与明确的计划?
如前所述,vc++拒绝模板化类的静态成员变量的声明/显式专门化语法是不正确的,还是允许但不是强制的诊断错误?
是的,这是vc++中的一个bug。在Visual Studio 2019 version 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
- 用C++中的一个变量定义一个常量
- 变量定义到C++布尔值转换
- 未声明的标识符错误,但变量定义正确 (?)
- C++多线程程序:变量定义为类成员的隔离错误
- 将变量定义为静态时,为什么可以多次定义它
- 初始值设定项列表与构造函数赋值与变量定义
- 如何根据传递给函数的变量定义特征矩阵大小
- C++ 描述如何使用来简化变量定义
- 如何转发声明依赖于变量定义的类,而变体定义又依赖于模板化类?
- 是否修改其声明语句中的变量定义良好
- 使用类的其他成员变量定义类的成员变量数组
- 是否可以通过全局变量定义具有可变参数数的函数中的参数数量
- 元组 std::get() 不适用于变量定义的常量
- 全局使用变量定义和C 声明
- C++变量定义中的易失性类型量词位置
- 将变量定义为数组和正确的语法
- 多个,包括具有变量定义的头文件
- 调用构造函数,并将成员作为参数解析为变量定义
- 逗号分隔变量定义中关系前的序列
- 如何使用命名变量定义常量右值引用参数的默认值