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)).
Мой вопрос к тебе:
- Это сохранить, чтобы использовать шаблон трюк? Какие недостатки?
- Было бы полезно смягчить определение 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;
который не является объявлением шаблона: само определение не является шаблонным, хотя определяемая вещь является специализацией шаблона.
Мой вопрос к тебе:
- Это сохранить, чтобы использовать шаблон трюк? Какие недостатки?
Здесь нет "хитрости". Если вы хотите определить участника для всех Foo<T>
, тогда у вас нет выбора, кроме как поместить определение в заголовочный файл. Если вы хотите определить члена для одного конкретного Foo<T>
такие как Foo<int>
, тогда вы не должны помещать определение в заголовочный файл (до C++17, который вводит встроенные переменные). Никаких хитростей нет, потому что то, что вы должны делать, зависит от вашей конкретной цели.
(Ваш второй вопрос был дан ответ в разделе комментариев.)