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

ODR of template class with static constexpr member

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

但我想知道,为什么使用模板 class 外联定义在头文件中有效,但不适用于专门的 class。

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.o 0000000000000100 g O .rodata 0000000000000004 _Z6FoobarIiE1aE

bar.o: 0000000000000420 g O .rodata 0000000000000004 _Z6FoobarIiE1aE

我们看到,外联定义在目标文件中有不同的地址(示例 b))。

我想问你的问题:

  1. 使用模板技巧是否省事?缺点是什么?
  2. 将来对于像 b 这样的情况放宽 odr 的定义是否有用?

提前致谢!

见[basic.def.odr]/6:

There can be more than one definition of a class type (Clause 9), enumeration type (7.2), inline function with external linkage (7.1.2), class template (Clause 14), non-static function template (14.5.6), static data member of a class template (14.5.1.3), member function of a class template (14.5.1.1), or template specialization for which some template parameters are not specified (14.7, 14.5.5) in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. ...

此规则的效果是每个 template-declaration 的行为都好像是内联的。 (但它不会扩展到 explicit-instantiationexplicit-specialization 声明。)

在第一个片段中,您有

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

这是一个template-declaration,因此允许被多重定义。在第二个片段中,您有

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

这不是 template-declaration:定义本身不是模板化的,即使被定义的东西恰好是模板的特化。

My question to you:

  1. Is it save to use the template trick? What are the disadvantage?

这里没有"trick"。如果要为allFoo<T>定义成员,那只能把定义放到头文件里了。如果你想为一个特定的Foo<T>定义成员,比如Foo<int>,那么你一定不能把定义放在头文件中(直到C++17 ,它引入了内联变量。)没有技巧,因为你应该做什么取决于你的具体目标。

(你的第二个问题已经在评论区回答了。)