Viatorus
Viatorus

Reputation: 1903

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!

Upvotes: 2

Views: 1679

Answers (1)

Brian Bi
Brian Bi

Reputation: 119239

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.)

Upvotes: 4

Related Questions