静态 constexpr 模板成员在专门化时给出未定义的引用
static constexpr template member gives undefined-reference when specialized
以下代码给出了一个未定义的引用链接错误:
template<int>
struct X {
static constexpr int x = 0;
};
template<>
constexpr int X<1>::x;
int main()
{
return X<1>::x;
}
但我不知道具体原因。
是否可以在不专门化整个模板的情况下定义数据成员?
明确一点:此代码编译正常,但会给出链接器错误(未定义引用)。
像这样。
template<int i>
struct X {
static constexpr int x = i==0?2:10;
};
int main()
{
return X<1>::x;
}
请注意,您不需要在 class 之外定义它。
Is it possible to define a data-member without [specializing] the whole template?
static
class 模板 的数据成员 允许显式特化 ([temp.expl.spec]),但是如果您愿意为此,您不能已经在 class 模板 (class.static.data) 中为成员指定初始值设定项。也就是说,
如果我们用const
替换constexpr
,这段代码就可以了:
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;
您可以看到不同之处在于我们为主模板初始化变量的位置。
现在,如果我们想用 constexpr
替换 const
,那么我们需要 提供初始化程序 (class.static.data) :
A static
data member of literal type can be declared in the
class definition with the constexpr
specifier; if so, its declaration shall specify a brace-or-equal-initializer
in which every initializer-clause that is an assignment-expression is a constant expression
所以我们最终会遇到这种奇怪的情况,我们可以特化 static
成员,但如果它是 constexpr
就不行,因为 constexpr
需要一个初始化程序。恕我直言,这是标准的一个缺点。
然而,似乎并非所有现代编译器都同意。
gcc 8.0.0 按原样编译(但不 link)您的代码(错误),但是如果您为专业化添加初始化程序,它会抱怨重复初始化(正确)。
clang 6.0.0 不会按原样编译代码(正确),但是当您添加初始化程序时它可以顺利运行(错误,但这可能是标准应该规定的)
MSVC 19.00.23506 不会按原样编译代码(右),并且在您添加初始化程序时不会编译代码(抱怨重定义)(右)。
最后,将专业化推入辅助特性可能会更容易 class:
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 来避免需要特化我们的特征 class:
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);
}
由于显式专业化,您偶然发现了 "small" 问题。如果我们参考 [temp.expl.spec]/13:
An explicit specialization of a static data member of a template or an
explicit specialization of a static data member template is a
definition if the declaration includes an initializer; otherwise, it
is a declaration. [ Note: The definition of a static data member of a
template that requires default-initialization must use a
braced-init-list:
template<> X Q<int>::x; // declaration
template<> X Q<int>::x (); // error: declares a function
template<> X Q<int>::x { }; // definition
— end note ]
意思是你声明 X<1>::x
存在,但没有定义它。因此它是未定义的。
我觉得疯狂的是,你的编译器只是接受它。通常,您不能在不定义变量的情况下声明 constexpr
变量。这很奇怪。
以下代码给出了一个未定义的引用链接错误:
template<int>
struct X {
static constexpr int x = 0;
};
template<>
constexpr int X<1>::x;
int main()
{
return X<1>::x;
}
但我不知道具体原因。
是否可以在不专门化整个模板的情况下定义数据成员?
明确一点:此代码编译正常,但会给出链接器错误(未定义引用)。
像这样。
template<int i>
struct X {
static constexpr int x = i==0?2:10;
};
int main()
{
return X<1>::x;
}
请注意,您不需要在 class 之外定义它。
Is it possible to define a data-member without [specializing] the whole template?
static
class 模板 的数据成员 允许显式特化 ([temp.expl.spec]),但是如果您愿意为此,您不能已经在 class 模板 (class.static.data) 中为成员指定初始值设定项。也就是说,
如果我们用const
替换constexpr
,这段代码就可以了:
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;
您可以看到不同之处在于我们为主模板初始化变量的位置。
现在,如果我们想用 constexpr
替换 const
,那么我们需要 提供初始化程序 (class.static.data) :
A
static
data member of literal type can be declared in the class definition with theconstexpr
specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression
所以我们最终会遇到这种奇怪的情况,我们可以特化 static
成员,但如果它是 constexpr
就不行,因为 constexpr
需要一个初始化程序。恕我直言,这是标准的一个缺点。
然而,似乎并非所有现代编译器都同意。
gcc 8.0.0 按原样编译(但不 link)您的代码(错误),但是如果您为专业化添加初始化程序,它会抱怨重复初始化(正确)。
clang 6.0.0 不会按原样编译代码(正确),但是当您添加初始化程序时它可以顺利运行(错误,但这可能是标准应该规定的)
MSVC 19.00.23506 不会按原样编译代码(右),并且在您添加初始化程序时不会编译代码(抱怨重定义)(右)。
最后,将专业化推入辅助特性可能会更容易 class:
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 来避免需要特化我们的特征 class:
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);
}
由于显式专业化,您偶然发现了 "small" 问题。如果我们参考 [temp.expl.spec]/13:
An explicit specialization of a static data member of a template or an explicit specialization of a static data member template is a definition if the declaration includes an initializer; otherwise, it is a declaration. [ Note: The definition of a static data member of a template that requires default-initialization must use a braced-init-list:
template<> X Q<int>::x; // declaration template<> X Q<int>::x (); // error: declares a function template<> X Q<int>::x { }; // definition
— end note ]
意思是你声明 X<1>::x
存在,但没有定义它。因此它是未定义的。
我觉得疯狂的是,你的编译器只是接受它。通常,您不能在不定义变量的情况下声明 constexpr
变量。这很奇怪。