简体   繁体   中英

ODR of template class with static constexpr member

I know, there are many answered question about linkage of a static (constexpr) members.

But I wonder, why using a template class out-of-line definition works in a header file but not for a specialized class.

a) This works without linker error:

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

The objdump of:

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

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

Linked file: 0000000000475a30 w O .rodata 0000000000000004 _Z6FoobarIiiE1aE

b) This does not (multiple definition):

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

The objdump of:

foo.o 0000000000000100 g O .rodata 0000000000000004 _Z6FoobarIiE1aE

bar.o: 0000000000000420 g O .rodata 0000000000000004 _Z6FoobarIiE1aE

What we see, the out-of-line definition has different addresses inside the object files (example b)).

My question to you:

  1. Is it save to use the template trick? What are the disadvantage?
  2. Would it be useful to relax the definition of odr for such cases like b in the future?

Thank you in advance!

See [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. ...

The effect of this rule is that every template-declaration behaves as though it is inline. (But it does not extend to explicit-instantiation and explicit-specialization declarations.)

In the first snippet, you have

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

which is a template-declaration and therefore is allowed to be multiply defined. In the second snippet, you have

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

which is not a template-declaration : the definition itself is not templated, even though the thing being defined happens to be a specialization of a template.

My question to you:

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

There is no "trick" here. If you want to define the member for all Foo<T> , then you have no choice but to put the definition in the header file. If you want to define the member for one specific Foo<T> such as Foo<int> , then you must not put the definition in the header file (until C++17, which introduces inline variables.) There is no trick because what you are supposed to do depends on your specific goal.

(Your second question was answered in the comment section.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM