Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initializing templated, recursive, POD struct

I was experimenting with using template recursion to generate a nested POD structs, and I came across some behavior I wasn't expecting. Here's a simplified test case:

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

I would expect this code to output

5
5

Indeed, that it what is generated when I compile with GCC 4.8.4 and 5.1.

However, compiling either with Clang (3.5 or 3.7) or Visual Studio 2010 instead results in

5
0

Is my code or my understanding of it wrong in some way, or do Clang and Visual Studio somehow both have bugs that result in the same erroneous output?

like image 853
rkjnsn Avatar asked Aug 19 '15 02:08

rkjnsn


1 Answers

I think both compilers are conforming as the order of initialization of static variables is unspecified. The clearest statement comes from a note in [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 ]

In our case, Builder<start, 1>::value was statically initialized, but everything else is dynamically uninitialized - so it's unspecified as to the fully initialized Builder<start, 1>::value is used or not.

A workaround is to use the construct on first use idiom and do something like (I took the liberty of pulling out Builder from RecursiveStruct for simplicity - it exhibits the same behavior either way):

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();

This prints 5 on both compilers.

like image 189
Barry Avatar answered Oct 21 '22 11:10

Barry