ODR шаблонного класса со статическим членом constexpr

Я знаю, что есть много ответов на вопрос о связывании статических (constexpr) членов.

Но мне интересно, почему использование внепланового определения класса шаблона работает в заголовочном файле, но не для специализированного класса.

а) Это работает без ошибки компоновщика:

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

б) Это не (множественное определение):

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. Было бы полезно смягчить определение odr для таких случаев, как b, в будущем?

Заранее спасибо!

1 ответ

Решение

Смотрите [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. Это сохранить, чтобы использовать шаблон трюк? Какие недостатки?

Здесь нет "хитрости". Если вы хотите определить участника для всех Foo<T>, тогда у вас нет выбора, кроме как поместить определение в заголовочный файл. Если вы хотите определить члена для одного конкретного Foo<T> такие как Foo<int>, тогда вы не должны помещать определение в заголовочный файл (до C++17, который вводит встроенные переменные). Никаких хитростей нет, потому что то, что вы должны делать, зависит от вашей конкретной цели.

(Ваш второй вопрос был дан ответ в разделе комментариев.)

Другие вопросы по тегам