静态 constexpr 模板成员在专用时提供未定义的引用

static constexpr template member gives undefined-reference when specialized

本文关键字:未定义 引用 专用 constexpr 成员 静态      更新时间:2023-10-16

以下代码给出了未定义的引用链接错误:

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)。那是

如果我们用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;

您可以看到不同之处在于我们初始化主模板变量的位置。

现在,如果我们希望将const替换为constexpr,那么我们需要提供一个初始值设定项 (class.static.data):

文本类型的static数据成员可以在 带有constexpr说明符的类定义;如果是这样,则其声明应指定一个大括号或等于初始值设定项,其中作为赋值表达式的每个初始值设定项子句都是常量表达式

因此,我们最终会陷入这种奇怪的情况,我们可以专门化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 来避免需要专门化我们的特质类:

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:

模板的静态数据成员的显式专用化或 静态数据成员模板的显式专用化是一个 定义,如果声明包含初始值设定项;否则,它 是一个声明。[ 注意:静态数据成员的定义 需要默认初始化的模板必须使用 大括号初始化列表:

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;
}

请注意,您不需要在类外定义它。