具有静态constexpr成员的模板类的ODR

ODR of template class with static constexpr member

本文关键字:ODR 成员 静态 constexpr      更新时间:2023-10-16

我知道,关于静态(constexpr)成员的链接,有很多问题得到了回答。

但我想知道,为什么在头文件中使用模板类的越界定义有效,而在专用类中却无效。

a) 这在没有链接器错误的情况下工作:

template<typename, typename>
struct Foobar;
template<typename T>
struct Foobar<int, T> {
static constexpr std::array<int, 1> a = {{1}};
};
template<typename T>
constexpr std::array<int, 1> Foobar<int, T>::a;
// foo.cpp
std::cout << Foobar<int, int>::a[0] << "n";
// bar.cpp
std::cout << Foobar<int, int>::a[0] << "n";

目标转储:

foo.o:0000000000000000 w O .rodata._Z6FoobarIiiE1aE 0000000000000004 _Z6FoobarIiiE1aE

bar.o:0000000000000000 w O .rodata._Z6FoobarIiiE1aE 0000000000000004 _Z6FoobarIiiE1aE

链接文件:0000000000475a30 w O .rodata 0000000000000004 _Z6FoobarIiiE1aE

b) 这不是(多重定义):

template<typename>
struct Foobar;
template<>
struct Foobar<int> {
static constexpr std::array<int, 1> a = {{1}};
};
constexpr std::array<int, 1> Foobar<int>::a;
// foo.cpp
std::cout << Foobar<int>::a[0] << "n";
// bar.cpp
std::cout << Foobar<int>::a[0] << "n";

目标转储:

foo.o0000000000000100 g O .rodata 0000000000000004 _Z6FoobarIiE1aE

bar.o:0000000000000420 g O .rodata 0000000000000004 _Z6FoobarIiE1aE

正如我们所看到的,越界的定义在对象文件中有不同的地址(示例b)。

我想问你的问题:

  1. 使用模板技巧是否保存?缺点是什么
  2. 在未来像b这样的情况下,放宽odr的定义会有用吗

提前谢谢!

参见[basic.def.odr]/6:

类类型(第9条)、枚举类型(7.2)、内联函数可以有多个定义外部链接(7.1.2)、类模板(第14条)、非静态函数模板(14.5.6)、静态数据成员类模板的成员函数(14.5.1.1),或如果每个定义出现在不同的翻译单元中,前提是定义满足以下要求。。。

此规则的效果是每个模板声明的行为都像是内联的。(但它没有扩展到显式实例化隐式专门化声明。)

在第一个片段中,您有

template<typename T>
constexpr std::array<int, 1> Foobar<int, T>::a;

这是一个模板声明,因此可以进行多重定义。在第二个片段中,您有

constexpr std::array<int, 1> Foobar<int>::a;

这不是模板声明:定义本身不是模板化的,即使所定义的东西恰好是模板的专门化。

我想问你的问题:

  1. 使用模板技巧是否保存?缺点是什么

这里没有"窍门"。如果您想为allFoo<T>定义成员,那么您别无选择,只能将定义放入头文件中。如果你想为一个特定的Foo<T>(如Foo<int>)定义成员,那么你不能把定义放在头文件中(直到C++17引入了内联变量。)没有什么诀窍,因为你应该做什么取决于你的特定目标。

(您的第二个问题已在评论部分得到回答。)