具有静态 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))。
我想问你的问题:
- 使用模板技巧是否省事?缺点是什么?
- 将来对于像 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-instantiation 和 explicit-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:
- Is it save to use the template trick? What are the disadvantage?
这里没有"trick"。如果要为allFoo<T>
定义成员,那只能把定义放到头文件里了。如果你想为一个特定的Foo<T>
定义成员,比如Foo<int>
,那么你一定不能把定义放在头文件中(直到C++17 ,它引入了内联变量。)没有技巧,因为你应该做什么取决于你的具体目标。
(你的第二个问题已经在评论区回答了。)
我知道,关于静态 (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))。
我想问你的问题:
- 使用模板技巧是否省事?缺点是什么?
- 将来对于像 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-instantiation 和 explicit-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:
- Is it save to use the template trick? What are the disadvantage?
这里没有"trick"。如果要为allFoo<T>
定义成员,那只能把定义放到头文件里了。如果你想为一个特定的Foo<T>
定义成员,比如Foo<int>
,那么你一定不能把定义放在头文件中(直到C++17 ,它引入了内联变量。)没有技巧,因为你应该做什么取决于你的具体目标。
(你的第二个问题已经在评论区回答了。)