2015-08-19 6 views
8

Eksperymentowałem z wykorzystaniem rekurencji szablonów do generowania zagnieżdżonych struktur POD i natrafiłem na pewne zachowanie, którego się nie spodziewałem. Oto uproszczony przypadek testowy:Inicjalizowanie szablonów, rekursywnych, POD struct

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

Spodziewam się ten kod do wyjścia

5 
5 

Rzeczywiście, że to, co jest generowany, gdy mogę skompilować z GCC 4.8.4 i 5.1.

Jednak kompilacji albo z Clang (3.5 lub 3.7) lub Visual Studio 2010 zamiast skutkuje

5 
0 

Czy mój kod lub moje rozumienie tego złego w jakiś sposób, czy brzęk i Visual Studio jakoś zarówno mieć błędy, które powodują takie same błędne wyniki?

Odpowiedz

3

Myślę, że oba kompilatory są zgodne, ponieważ kolejność inicjowania zmiennych statycznych jest nieokreślona. Najczystsze stwierdzenie pochodzi z notatki w [basic.start.init]:

[Uwaga: W konsekwencji, jeżeli inicjalizacji obj1 obiektu odnosi się do obiektu obj2 zakresu przestrzeni nazw potencjalnie wymagających dynamicznego inicjalizacji i zdefiniowane później w tej samej jednostce tłumaczeniowej, nie jest określone, czy użyta wartość obj2 będzie wartością w pełni zainicjowanego obj2 (ponieważ obiekt został statycznie zainicjowany jako ), czy też będzie wartością obj2, a jedynie inicjalizacją zerową. Na przykład,

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 

końcem uwaga]

W naszym przypadku Builder<start, 1>::value została statycznie zainicjowany, ale wszystko inne jest dynamicznie niezainicjowanymi - więc jest to nieokreślone co do w pełni zainicjowany Builder<start, 1>::value jest używany lub nie.

Rozwiązaniem jest użycie konstruktu przy pierwszym użyciu idiomu i zrobić coś takiego (wziąłem wolność wyciągając Builder z RecursiveStruct dla uproszczenia - wykazuje takie samo zachowanie w obu kierunkach):

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

ten drukuje 5 na obu kompilatorach.

+0

Jeśli nie zrozumiem, nie sądzę, że ta uwaga miałaby tutaj zastosowanie. Rozumiem, że niezdefiniowana inicjalizacja dotyczy dynamicznej inicjalizacji. Inicjalizacja ma miejsce podczas uruchamiania programu. Jak rozumiem, powodem, dla którego podany przykład jest nieokreślony, jest to, że 'fd()' może lub nie może być wstawiony przez kompilator, zatem 'd1' może być inicjowane dynamicznie lub statycznie, dlatego' d2' jest nieokreślone. W moim przypadku wszystko jest const POD, więc wszystkie powinny być statycznie zainicjalizowane. – rkjnsn

+0

Może to być nadal nieokreślone zachowanie, ale jeśli tak, to nie sądzę, że jest to odnośnik, który tak czyni. – rkjnsn

+0

@rkjnsn Inicjalizacja statyczna wymaga, aby została zainicjalizowana za pomocą stałego wyrażenia - dlatego niektóre obiekty są inicjalizowane dynamicznie. – Barry