以下代码给出了未定义的引用链接错误:
template<int>
struct X {
static constexpr int x = 0;
};
template<>
constexpr int X<1>::x;
int main()
{
return X<1>::x;
}
但我不确切知道为什么。
是否可以定义数据成员而不专门化整个模板?
要明确:此代码编译良好,但提供链接器错误(未定义引用)。
是否可以在没有[专门化]整个模板的情况下定义数据成员?
允许对类模板的static
数据成员进行显式专门化([temp.expl.spec]),但是如果您希望这样做,则无法在类模板中为该成员指定初始化程序(class.static.data) )。那是,
如果我们用constexpr
替换const
,这段代码就可以了:
template<int>
struct X {
static const int x;
};
template<int I>
const int X<I>::x = 0;
template<>
const int X<1>::x = 1;
但是这段代码不会很好:
template<int>
struct X {
static const int x = 0;
};
template<>
const int X<1>::x = 1;
您可以看到区别在于我们初始化主模板的变量。
现在,如果我们希望用const
替换constexpr
,那么我们需要提供一个初始化器(class.static.data):
可以使用
static
说明符在类定义中声明文字类型的constexpr
数据成员;如果是这样,它的声明应该指定一个大括号或者相等的初始化器,其中作为赋值表达式的每个initializer子句都是一个常量表达式
因此,我们最终会遇到奇怪的情况,我们可以专注于static
成员,但如果它是constexpr
则不会因为constexpr
需要初始化器。恕我直言,这是标准的缺点。
然而,似乎所有现代编译器都不同意。
gcc 8.0.0按原样编译(但不链接)你的代码(错误),但是如果为专门化添加初始化程序,它会抱怨重复初始化(右)。
clang 6.0.0不会按原样(右)编译代码,但是当你添加初始化程序时,它可以毫无障碍地工作(错误,但这可能是标准应该指定的)
MSVC 19.00.23506不按原样编译代码(右),并且在添加初始化程序时没有编译代码(抱怨重定义)(右)。
最后,将专业化推入辅助Traits类可能更容易:
template<int>
struct X_Traits{
static constexpr int value = 0;
};
template<>
struct X_Traits<1>{
static constexpr int value = 1;
};
template<int I>
struct X {
static constexpr int x=X_Traits<I>::value;
// ...
};
在C ++ 17及更高版本中,我们可以使用constexpr if来避免需要专门化我们的traits类:
template<int I>
struct X_Traits{
static constexpr int get_value(){
if constexpr(I==1){
return 1;
}else{
return 0;
}
}
};
template<int I>
struct X {
static constexpr int x=X_Traits<I>::get_value();
// ...
};
int main(){
static_assert(X<0>::x == 0);
static_assert(X<1>::x == 1);
}
由于明确专业化,你偶然发现了一个“小”问题。如果我们引用[temp.expl.spec]/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
- 结束说明]
这意味着您声明X<1>::x
存在,但没有定义它。因此它未定义。
我觉得很疯狂的是你的编译器只接受它。一般而言,您无法在不定义constexpr
变量的情况下声明它们。这很奇怪。
像这样。
template<int i>
struct X {
static constexpr int x = i==0?2:10;
};
int main()
{
return X<1>::x;
}
请注意,您不需要在课外定义它。