为什么静态const char * template struct成员未初始化

时间:2015-10-05 12:39:51

标签: c++ templates clang constexpr visual-c++-2015

我有一些C ++ 11模板代码我试图移植到Visual C ++ Compiler 2015.原始代码工作正常,但我需要重写它以解决constexpr的问题。

The original code (simplified example)

#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

但是,当我重写时,一些静态变量未初始化。即使它编译得很好。

The ported code (not working)

#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. 如何修复/解决它?

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

    更新:Working solution

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

2 个答案:

答案 0 :(得分:2)

我认为问题来自[basic.start.init]:

  

如果变量是隐式或显式实例化的专门化,则静态存储持续时间的非局部变量的动态初始化无序

Derived<Base>::valueFoo<BarType>::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 :(得分:1)

@Barry解释了问题的原因。

可能的解决方法是强制初始化顺序。由于String不是模板化类,因此在动态初始化发生之前,String::value将被正确初始化(静态)。

我可以想象两种方式:

  1. 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;
    }
    
  2. 在Foo中使value成为一个函数:

    ...
    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;
    }