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++ 中重现,但是 GCCFooBar::value = STRING也会在第二个示例中打印。

更新:工作解决方案

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

4

2 回答 2

2

我认为问题来自[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. 您看到的分段错误是由于尝试流式传输空字符指针而导致的。

于 2015-10-05T13:29:11.060 回答
1

@Barry 给出了问题的原因。

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

我可以想象两种方式:

  1. 添加显式 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;
    }
    
  2. valueFoo 中创建一个函数:

    ...
    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;
    }
    
于 2015-10-05T14:50:15.330 回答