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

时间:2017-02-14 07:57:29

标签: c++ c++11 c++14 constexpr one-definition-rule

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

但我想知道,为什么使用模板类的外行定义在头文件中工作,但不适用于专门的类。

a)这没有链接器错误:

template<typename, typename>
struct Foobar;

template<typename T>
struct Foobar<int, T> {
  static constexpr std::array<int, 1> a = 0000000000000000  w    O .rodata._Z6FoobarIiiE1aE    0000000000000004 _Z6FoobarIiiE1aE;
};

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

objdump:

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

bar.o:0000000000475a30 w O .rodata 0000000000000004 _Z6FoobarIiiE1aE

链接文件:template<typename> struct Foobar; template<> struct Foobar<int> { static constexpr std::array<int, 1> a = 0000000000000100 g O .rodata 0000000000000004 _Z6FoobarIiE1aE; }; 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";

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

0000000000000420 g     O .rodata        0000000000000004 _Z6FoobarIiE1aE

objdump:

foo.o {{1}}

bar.o:{{1}}

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

我向你提问:

  1. 使用模板技巧是否可以保存?有什么缺点?
  2. 将来像b这样的案例放宽odr的定义会有用吗?
  3. 提前谢谢!

1 个答案:

答案 0 :(得分:3)

见[basic.def.odr] / 6:

  

类类型(第9条),枚举类型(7.2),内联函数可以有多个定义   外部链接(7.1.2),类模板(第14章),非静态函数模板(14.5.6),静态数据成员   类模板(14.5.1.3),类模板的成员函数(14.5.1.1)或模板特化   其中一些模板参数未指定(14.7,14.5.5),在程序中提供了每个定义   出现在不同的翻译单元中,并且定义满足以下要求。 ...

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

在第一个代码段中,您有

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

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

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

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

  

我向你提问:

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

没有&#34;技巧&#34;这里。如果要为 all Foo<T>定义成员,那么您别无选择,只能将定义放在头文件中。如果要为一个特定的 Foo<T>定义成员,例如Foo<int>,那么你不能将定义放在头文件中(直到C ++ 17,它引入了内联变量。)没有技巧,因为你应该做的事情取决于你的具体目标。

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