初始化模板化、递归、POD结构体。

8

我正在尝试使用模板递归来生成嵌套的POD结构体,但我遇到了一些意外的行为。这里是一个简化的测试案例:

#include <cstddef>

template<std::size_t size>
struct RecursiveStruct {
public:
    template <std::size_t start, std::size_t length>
    struct Builder {
        static const Builder value;
        static const size_t mid = start + length / 2;
        static const size_t end = start + length;
        Builder<start, mid - start> left;
        Builder<mid, end - mid> right;
    };

    template <std::size_t start>
    struct Builder<start, 1> {
        static const Builder value;
        int data;
    };

    static const Builder<0, size> result;
};

template<std::size_t size>
const typename RecursiveStruct<size>::template Builder<0, size>
        RecursiveStruct<size>::result = Builder<0, size>::value;

template<std::size_t size>
template<std::size_t start, std::size_t length>
const typename RecursiveStruct<size>::template Builder<start, length>
        RecursiveStruct<size>::Builder<start, length>::value
            = { Builder<start, mid - start>::value, Builder<mid, end - mid>::value };

template<std::size_t size>
template <std::size_t start>
const typename RecursiveStruct<size>::template Builder<start, 1>
        RecursiveStruct<size>::Builder<start, 1>::value = { 5 };

////////////////////////////////////////////////////////

#include <iostream>

using std::cout;
using std::endl;
using std::size_t;

int main() {
    cout << RecursiveStruct<1>::result.data << endl;
    cout << RecursiveStruct<2>::result.left.data << endl;
    return 0;
}

我希望这段代码能够输出

5
5

使用GCC 4.8.4和5.1编译时会生成这个。

但是,使用Clang(3.5或3.7)或Visual Studio 2010进行编译则会产生不同的结果。

5
0

我的代码或者我的理解方式哪里有问题,还是Clang和Visual Studio都有错误导致出现相同的错误输出?
1个回答

3

我认为两个编译器都是符合规范的,因为静态变量初始化的顺序是未指定的。最清晰的说明来自[basic.start.init]中的一条注释:

[ Note: As a consequence, if the initialization of an object obj1 refers to an object obj2 of namespace scope potentially requiring dynamic initialization and defined later in the same translation unit, it is unspecified whether the value of obj2 used will be the value of the fully initialized obj2 (because obj2 was statically initialized) or will be the value of obj2 merely zero-initialized. For example,

inline double fd() { return 1.0; }
extern double d1;
double d2 = d1; // unspecified:
// may be statically initialized to 0.0 or
// dynamically initialized to 0.0 if d1 is
// dynamically initialized, or 1.0 otherwise
double d1 = fd(); // may be initialized statically or dynamically to 1.0

—end note ]

在我们的情况下,Builder<start, 1>::value 是静态初始化的,但其他所有内容都是动态未初始化的 - 因此无法确定是否使用了完全初始化的 Builder<start, 1>::value
一种解决方法是使用首次使用惯用语,并执行类似以下操作(为简单起见,我从 RecursiveStruct 中提取了 Builder - 无论哪种方式都会展示相同的行为):
template <std::size_t start, std::size_t length>
struct Builder
{
    static const size_t mid = start + length / 2;
    static const size_t end = start + length;    

    static const Builder value() {
        static const Builder value_{ 
            Builder<start, mid - start>::value(), 
            Builder<mid, end - mid>::value() 
        };
        return value_;
    }

    Builder<start, mid - start> left;
    Builder<mid, end - mid> right;
};

template <std::size_t start>
struct Builder<start, 1> {
    static const Builder value() {
        static const Builder value_{5};
        return value_;
    }

    int data;
};

template<std::size_t size>
struct RecursiveStruct {
public:
    static const Builder<0, size> result;
};

template <std::size_t size>
const Builder<0, size> RecursiveStruct<size>::result = Builder<0, size>::value();

这将在两个编译器上打印5


除非我误解了,否则我认为这个注释不适用于这里。我的理解是未定义的初始化适用于动态初始化。也就是说,在程序启动时发生的初始化。据我所知,给出的示例未定义的原因是因为fd()可能会被编译器内联或不内联,因此d1可以是动态初始化或静态初始化,这就是为什么d2是未指定的原因。在我的情况下,一切都是const POD,所以应该全部静态初始化。 - rkjnsn
可能仍然是未指定的行为,但如果是这样,我不认为是参考注释使其成为未指定的行为。 - rkjnsn
@rkjnsn 静态初始化要求使用常量表达式进行初始化 - 因此您的一些对象是动态初始化的。 - Barry
嗯...好的。那很有道理,但这真是不幸。对于我的用例,我确实需要它成为一个静态初始化常量。我知道C++11有constexpr,可能可以做到,但在C++03中有没有任何方法呢?(也许这应该是一个新问题。) - rkjnsn
@rkjnsn 不认为在C++03中可以这样做,但问一下也无妨! - Barry

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