2015-08-19 8 views
8

Ich experimentierte mit Vorlagen Rekursion, um eine verschachtelte POD-Strukturen zu generieren, und ich stieß auf etwas Verhalten, das ich nicht erwartet hatte. Hier ist ein vereinfachtes Testfall:Initialisieren templated, rekursive, POD-Struktur

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

Ich würde erwarten, diesen Code zur Ausgabe

5 
5 

der Tat, dass es, was erzeugt wird, wenn ich mit GCC 4.8.4 und 5.1 zu kompilieren.

jedoch Kompilieren entweder mit Clang (3.5 oder 3.7) oder Visual Studio 2010 führt stattdessen in

5 
0 

Ist mein Code oder mein Verständnis davon falsch in irgendeiner Weise, oder tun Clang und Visual Studio irgendwie beide haben Fehler, die zu der gleichen fehlerhaften Ausgabe führen?

Antwort

3

Ich denke, dass beide Compiler konform sind, da die Reihenfolge der Initialisierung von statischen Variablen nicht angegeben ist. Die klarste Aussage stammt aus einer Notiz in [basic.start.init]:

[Anmerkung: Als Folge, wenn die Initialisierung eines Objekts obj1 auf ein Objekt verweist obj2 von Namespacebereich potenziell dynamische erfordern Initialisierung und später in der gleichen Übersetzungseinheit definiert, ist es nicht spezifiziert , ob der Wert von obj2 der Wert des vollständig initialisierten obj2 sein wird (weil obj2 statisch initialisiert wurde) oder wird der Wert von obj2 lediglich null-initialisiert. Zum Beispiel

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 

-Ende note]

In unserem Fall wurde Builder<start, 1>::value statisch initialisiert, aber alles andere dynamisch nicht initialisiert ist - so ist es nicht spezifiziert in Bezug auf die vollständig initialisiert Builder<start, 1>::value verwendet wird, oder nicht.

Eine Abhilfe ist das Konstrukt bei der ersten Verwendung Idiom zu verwenden und so etwas wie (ich mir die Freiheit nahm von Builder aus RecursiveStruct der Einfachheit halber herausziehen - es zeigt das gleiche Verhalten oder so):

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

Diese druckt 5 auf beiden Compilern.

+0

Wenn ich nicht falsch verstanden habe, glaube ich nicht, dass dieser Hinweis hier gelten würde. Nach meinem Verständnis gilt für die dynamische Initialisierung eine undefinierte Initialisierung. Das heißt, Initialisierung beim Programmstart. Wie ich es verstehe, ist der Grund dafür, dass das angegebene Beispiel nicht definiert ist, weil 'fd()' vom Compiler inline sein kann oder nicht, daher kann "d1" entweder dynamisch oder statisch initialisiert werden, weshalb "d2" nicht spezifiziert ist. In meinem Fall ist alles const POD, also sollte alles statisch initialisiert werden. – rkjnsn

+0

Es kann immer noch unspezifisches Verhalten sein, aber wenn dem so ist, glaube ich nicht, dass es der Referenzhinweis ist, der es so macht. – rkjnsn

+0

@rkjnsn Die statische Initialisierung erfordert, dass sie mit einem konstanten Ausdruck initialisiert wird - so dass einige Ihrer Objekte dynamisch initialisiert werden. – Barry