У меня есть некоторый код шаблона C ++ 11, который я пытаюсь перенести на Visual C ++ Compiler 2015. Оригинальный код прекрасно работает, однако мне нужно переписать его, чтобы обойти проблемы с constexpr.
Оригинальный код (упрощенный пример)
#include <iostream>
struct String
{
static constexpr const char * value{ "STRING" };
};
template<typename Base>
class Derived
{
public:
static constexpr const char * value{ Base::value };
};
template<typename BarType>
struct Foo
{
static constexpr const char * value{ BarType::value };
};
using Bar = Derived<String>;
using FooBar = Foo<Bar>;
int main()
{
std::cout << "FooBar::value = " << FooBar::value << std::endl;
}
Это печатает:
FooBar::value = STRING
Однако, когда я переписываю, некоторые статические переменные не инициализируются. Даже если он компилируется просто отлично.
Портированный код (не работает)
#include <iostream>
struct String
{
static const char * value;
};
const char * String::value = "STRING";
template<typename Base>
class Derived
{
public:
static const char * value;
};
template<typename Base>
const char * Derived<Base>::value = { Base::value };
template<typename BarType>
struct Foo
{
static const char * value;
};
template<typename BarType>
const char * Foo<BarType>::value = { BarType::value };
using Bar = Derived<String>;
using FooBar = Foo<Bar>;
int main()
{
std::cout << "FooBar::value = " << FooBar::value << std::endl;
}
Это печатает:
// nothing (Segmentation fault)
Почему это происходит?
Как мне исправить / обойти это?
Это может быть воспроизведено в Clang и Visual-C ++, однако GCC печатает FooBar::value = STRING
также во втором примере.
Обновить: Рабочий раствор
По предложению @ serge-ballesta. Я предпочитаю это решение, так как оно очень похоже на оригинальный код. Легко наносится и снова удаляется при добавлении членов constexpr в VS.
Я думаю, что проблема исходит от [basic.start.init]:
Динамическая инициализация нелокальной переменной со статической продолжительностью хранения неупорядоченный если переменная является неявно или явно конкретизированной специализацией
Инициализация Derived<Base>::value
а также Foo<BarType>::value
не являются статической инициализацией — потому что правая часть не является константным выражением. Это делает его динамической инициализацией. Поскольку переменные являются шаблонными специализациями, инициализация неупорядоченный — то есть не существует явно определенного порядка для двух value
s.
Таким образом, у нас есть два возможных заказа. Действительный:
Derived<Base>::value ==> 0
Foo<BarType>::value ==> 0
Derived<Base>::value ==> Base::value
Foo<BarType>::value ==> BarType::value
И недействительный:
Derived<Base>::value ==> 0
Foo<BarType>::value ==> 0
Foo<BarType>::value ==> BarType::value
Derived<Base>::value ==> Base::value
Если Derived<Base>::value
сначала инициализируется, затем Foo<BarType>::value
будет указывать на "STRING"
, В противном случае, если последний инициализируется первым, он будет инициализирован как 0
, Ошибка сегментации, которую вы видите в результате попытки потоковой передачи нулевого символьного указателя.
@ Барри объяснил причину проблемы.
Возможным обходным решением будет принудительный порядок инициализации. Как String
это не шаблонный класс, String::value
будет правильно инициализирован (статически) до динамической инициализации.
Я могу представить 2 способа:
Добавьте явный метод init к Foo
вместо зависимости от автоматической динамической инициализации:
...
template<typename BarType>
struct Foo
{
static const char * value;
static void init() {
Foo::value = BarType::value;
}
};
template<typename BarType>
const char * Foo<BarType>::value;
using Bar = Derived<String>;
using FooBar = Foo<Bar>;
int main()
{
FooBar::init();
std::cout << "FooBar::value = " << FooBar::value << std::endl;
}
Делать value
функция в Foo:
...
template<typename BarType>
struct Foo
{
static const char * value() {
return BarType::value;;
}
};
using Bar = Derived<String>;
using FooBar = Foo<Bar>;
int main()
{
std::cout << "FooBar::value = " << FooBar::value() << std::endl;
}