为什么静态const char *模板结构成员没有初始化

4
我有一些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)
  1. 为什么会发生这种情况?

  2. 我该如何修复/解决它?

这可以在Clang和Visual-C++中重现,然而GCC在第二个例子中也打印出FooBar::value = STRING

更新:可行的解决方案

如@serge-ballesta所建议的那样。我更喜欢这个解决方案,因为它非常类似于原始代码。当constexpr成员添加到VS时,易于应用和轻松删除。


3
为什么会发生这种情况?输出的STRING不符合您的预期吗? - Lightness Races in Orbit
1
在使用之前,通过 template class Derived<String>; template struct Foo<Derived<String>>; 显式实例化这两个类可以解决 segfault 的问题。 - Piotr Skotnicki
1
@LightnessRacesinOrbit 我相信输出的nothing不是原帖作者所期望的结果。 - Barry
3
是的,我删除了链接,因为代码应该在问题中。而且,你知道,有问题行为的实际描述也应该在问题中。 - Barry
1
@Barry,同意!但链接也应该在那里,这样可以节省所有读者编译的时间。 - Mathias
显示剩余17条评论
2个回答

2
我认为问题在于[basic.start.init]:
动态初始化静态存储持续期的非局部变量是无序的,如果该变量是隐式或显式实例化的特化。
Derived::value和Foo::value的初始化不是静态初始化,因为右边不是常量表达式。这使得它们成为了动态初始化。由于这些变量是模板特化,因此它们的初始化是无序的,即对于两个value没有明确定义的排序方式。
因此,我们有两种可能的排序。有效的排序如下:
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。你看到的段错误是由于尝试流式传输空字符指针造成的。

1

@Barry解释了问题的原因。

一个可能的解决方法是强制初始化顺序。由于String不是模板类,所以String::value将在动态初始化之前正确地进行静态初始化。

我可以想象两种方法:

  1. Add an explicit init method to Foo instead of depending on automatic dynamic initialization:

    ...
    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;
    }
    
  2. Make value a function in 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;
    }
    

谢谢!我将把@Barry的答案标记为正确,因为问题中最有趣的部分是“为什么”。你的第二个解决方案非常类似于原始代码。我认为这是最好的方法,当constexpr在VS中成熟时,撤销更改将很容易。我正在向问题中添加一个解决方案示例。 - Mathias

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接