Сначала пара решений. Для обоих из них важной частью является получение адреса registrar
из кода, который гарантированно будет создан. Это гарантирует, что определение статического члена также будет создано, вызывая побочный эффект.
Первый основан на том факте, что определение конструктора по умолчанию для каждой специализации Foo
создается для обработки инициализации по умолчанию a
, b
и c
в main
:
template<typename T> class Foo
{
public:
Foo() { (void)&RegistrarWrapper<Foo<T>>::registrar; }
};
Недостатком является то, что это вводит нетривиальный конструктор. Альтернативой, позволяющей избежать этой проблемы, является следующее:
template<class T> constexpr std::size_t register_class()
{
(void)&RegistrarWrapper<T>::registrar;
return 1;
}
template<typename T> class Foo
{
static char reg[register_class<Foo<T>>()];
};
Ключевым моментом здесь является запуск создания экземпляра в объявлении статического члена, не полагаясь на какой-либо инициализатор (см. ниже).
Оба решения отлично работают в Clang 3.7.0, GCC 5.2.0 и Visual C++ 2015 как с включенной оптимизацией, так и без нее. Второй использует расширенные правила для функций constexpr
, которые являются функцией C++14. Конечно, есть несколько простых способов сделать его совместимым с C++11, если это необходимо.
Я думаю, что проблема с вашим решением заключается в том, что нет гарантии, что инициализатор для __reg_ptr
будет создан, если его значение где-то не используется. Некоторые стандартные цитаты из N4527:
14.7.1p2:
[...] инициализация (и любые связанные с ней побочные эффекты) статического члена данных не происходит, если сам статический член данных не используется таким образом, который требует определения статического члена данных для существования.
Это не совсем относится к случаю constexpr
, поскольку (я думаю) речь идет о внеклассовом определении статического члена данных, который используется odr (это больше относится к registrar
), но это близко.
14.7.1p1:
[...] Неявное создание экземпляра специализации шаблона класса вызывает неявное создание экземпляров объявлений, но не определений, аргументов по умолчанию или спецификаций исключений функций-членов класса, классов-членов, перечислений членов с ограниченной областью, статических элементов данных и шаблоны участников [...]
Это гарантирует, что второе решение работает. Обратите внимание, что он ничего не гарантирует относительно инициализатора в классе для статического члена данных.
Кажется, есть некоторая неопределенность в отношении создания экземпляров конструкций constexpr
. Есть CWG 1581, что не так важно к нашему случаю, за исключением того, что в самом конце говорится о том, что неясно, происходит ли инстанцирование constexpr
во время вычисления константного выражения или во время парсинга. Некоторые разъяснения в этой области могут предоставить некоторые гарантии и для вашего решения (в любом случае...), но нам придется подождать.
Третий вариант: способ заставить ваше решение работать — это явно создавать экземпляры специализаций Foo
вместо того, чтобы полагаться на неявные экземпляры:
template class Foo<int>;
template class Foo<bool>;
template class Foo<std::string>;
int main()
{
for(auto&& data : test_vector()) {
std::cout << data.name() << std::endl;
}
}
Это также работает во всех трех компиляторах и зависит от 14.7.2p8:
Явное создание экземпляра, которое называет специализацию шаблона класса, также является явным созданием экземпляра того же типа (объявление или определение) каждого из его членов [...]
Учитывая, что это явные определения создания экземпляров, кажется, этого достаточно, чтобы убедить GCC создать экземпляр инициализатора для __reg_ptr
. Однако эти явные определения инстанцирования могут появляться во всей программе только один раз ([14.7p5.1]), поэтому требуется дополнительная осторожность. Я считаю первые два решения более надежными.
person
bogdan
schedule
03.11.2015
Foo() { (void)&RegistrarWrapper<Foo<T>>::registrar; }
(и избавление от членаconstexpr
) решает эту проблему во всех трех компиляторах и не требует дополнительных затрат времени выполнения по сравнению с вашим исходным решением. Еще несколько идей здесь: stackoverflow.com/a/27965993/4326278. - person bogdan   schedule 03.11.2015Foo
как обычные старые данные, поскольку теперь у него есть определяемый пользователем конструктор по умолчанию. - person Daisy Sophia Hollman   schedule 03.11.2015static char reg[register_class<Foo<T>>()];
, гдеregister_class
— это шаблон функцииconstexpr
, который выполняет танец(void)&RegistrarWrapper<T>::registrar;
в своем теле и возвращает, скажем,1
. - person bogdan   schedule 03.11.2015