2015-07-16 9 views
15

Betrachten Sie den folgenden Code ein:Initialisierung von std :: array <>

#include <array> 

struct A 
{ 
    int a; 
    int b; 
}; 

static std::array<A, 4> x1 = 
{ 
     { 1, 2 }, 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 } 
}; 

static std::array<A, 4> x2 = 
{ 
    { 
     { 1, 2 }, 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 } 
    } 
}; 

static std::array<A, 4> x3 = 
{ 
     A{ 1, 2 }, 
     A{ 3, 4 }, 
     A{ 5, 6 }, 
     A{ 7, 8 } 
}; 

static std::array<A, 4> x4 = 
{ 
     A{ 1, 2 }, 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 } 
}; 

mit gcc kompilieren:

$ gcc -c --std=c++11 array.cpp 
array.cpp:15:1: error: too many initializers for ‘std::array<A, 4ul>’ 
}; 
^ 
$ 

NB1: der Erstinitialisierung Aussage kommentierte heraus kompiliert der Code ohne Fehler.
NB2: Die Konvertierung aller Initialisierungen in Konstruktoraufrufe führt zu denselben Ergebnissen.
NB3: MSVC2015 verhält sich genauso.

Ich kann sehen, warum die erste Initialisierung nicht kompiliert, und warum die zweite und dritte in Ordnung sind. (Siehe z. B. C++11: Correct std::array initialization?.)

Meine Frage ist: Warum kompiliert die endgültige Initialisierung genau?

+0

Es tut mir leid, aber ich kann nicht sehen, warum die erste Aufgabe nicht kompiliert, können Sie mir bitte mehr sagen? Es ist interessant ! – Telokis

+1

@Ninetainedo - siehe die verknüpfte Frage. – Jeremy

+0

@dyp - Korrigiert. – Jeremy

Antwort

27

Kurzversion: Eine Initialisierungsklausel, die mit { beginnt, stoppt brace-elision. Dies ist der Fall im ersten Beispiel mit {1,2}, aber nicht in der dritten oder vierten, die A{1,2} verwenden. Brace-elision verbraucht die nächsten N Initialisierer-Klauseln (wobei N von dem zu initialisierenden Aggregat abhängt), weshalb nur die erste Initialisierer-Klausel des N nicht mit { beginnen darf.


In allen Implementierungen der C++ Standard Library ich kenne, std::array ist eine Struktur, die eine C-Stil-Array enthält. Das heißt, Sie ein Aggregat haben, die enthält eine Unter Aggregat, ähnlich wie

template<typename T, std::size_t N> 
struct array 
{ 
    T __arr[N]; // don't access this directly! 
}; 

Wenn ein std::array von einem verspannt-init-Liste Initialisieren Sie daher die Mitglieder der initialisieren müssen enthaltenes Array. Daher wird auf diesen Implementierungen kann die explizite Form ist:

std::array<A, 4> x = {{ {1,2}, {3,4}, {5,6}, {7,8} }}; 

Die äußerste Reihe von Klammern bezieht sich auf die std::array struct; Die zweite Gruppe von geschweiften Klammern bezieht sich auf das verschachtelte C-artige Array.

C++ ermöglicht in Aggregat-Initialisierung, bestimmte geschweifte Klammern beim Initialisieren verschachtelter Aggregate wegzulassen. Zum Beispiel:

struct outer { 
    struct inner { 
     int i; 
    }; 
    inner x; 
}; 

outer e = { { 42 } }; // explicit braces 
outer o = { 42 }; // with brace-elision 

Die Regeln sind wie folgt (eine Post-N4527 Entwurf verwendet wird, die post-C++ 14, aber 11 C++ einen Defekt enthalten auf diese ohnehin bezogen):

Klammern können in einer Initialisierungsliste wie folgt entfernt werden. Wenn die Initialisierer-Liste mit einer linken Klammer beginnt, dann die nachfolgenden durch Kommata getrennte Liste von initializer-Klauseln initialisiert die Mitglieder der ein Unteraggregat; es ist falsch, dass es mehr Initialisierungsklauseln als Mitglieder gibt.Wenn jedoch die Initialisierer-Liste für eine Unteraggregat beginnt nicht mit einer linken Klammer, dann nur genug initializer-Klauseln aus der Liste genommen, um die Mitglieder des Unteraggregats zu initialisieren; alle verbleibenden initializer-Klauseln sind übrig, um das nächste Element des Aggregats zu initialisieren, von dem das aktuelle Unteraggregat ein Mitglied ist.

Angewandt auf dem ersten std::array -Beispiel:

static std::array<A, 4> x1 = 
{ 
     { 1, 2 }, 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 } 
}; 

Dies wird wie folgt interpretiert:

static std::array<A, 4> x1 = 
{  // x1 { 
    {  // __arr { 
    1, //  __arr[0] 
    2 //  __arr[1] 
     //  __arr[2] = {} 
     //  __arr[3] = {} 
    }  // } 

    {3,4}, // ?? 
    {5,6}, // ?? 
    ... 
};  // } 

Die erste { als initializer der std::array Struktur genommen wird. Die Initialisiererklauseln{1,2}, {3,4} usw. werden dann als die Initialisierer der Unteraggregate von std::array genommen. Beachten Sie, dass nur ein einziges Unteraggregat __arr hat. Seit der ersten initializer-Klausel{1,2} beginnt mit einem {, die Klammer-elision Ausnahme nicht auftritt, und der Compiler versucht, die verschachtelten A __arr[4] Array mit {1,2} zu initialisieren. Die restlichen Initialisierungsklauseln{3,4}, {5,6} usw. beziehen sich nicht auf Unteraggregate std::array und sind daher illegal.

Im dritten und vierten Beispiel ist die erste initializer-Klausel für das Unteraggregat von std::arraynicht mit einem { beginnen, damit die Klammer elision Ausnahme angewandt wird:

static std::array<A, 4> x4 = 
{ 
     A{ 1, 2 }, // does not begin with { 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 } 
}; 

So ist es wie folgt interpretiert:

static std::array<A, 4> x4 = 
    {    // x4 { 
       // __arr {  -- brace elided 
    A{ 1, 2 }, //  __arr[0] 
    { 3, 4 }, //  __arr[1] 
    { 5, 6 }, //  __arr[2] 
    { 7, 8 } //  __arr[3] 
       // }    -- brace elided 
    };   // } 

Daher sind die A{1,2} bewirkt, dass alle vier initializer-Klauseln werden verbraucht, um das verschachtelte C-artige Array zu initialisieren. Wenn Sie einen anderen initializer hinzufügen:

static std::array<A, 4> x4 = 
{ 
     A{ 1, 2 }, // does not begin with { 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 }, 
     X 
}; 

dann diese X würde verwendet werden, um die nächste Unteraggregat von std::array zu initialisieren. Z.B.

struct outer { 
    struct inner { 
     int a; 
     int b; 
    }; 

    inner i; 
    int c; 
}; 

outer o = 
    {  // o { 
      // i { 
    1,  //  a 
    2,  //  b 
      // } 
    3  // c 
    };  // } 

Brace-elision verbraucht der nächsten N-Initialisierer-Klauseln, wobei N durch die Anzahl von Initialisierungen für die (Unter-) Aggregat erforderlich ist definiert initialisiert werden. Daher ist es nur wichtig, ob die erste dieser N Initialisierungsklauseln mit einer { beginnt oder nicht.

Weitere ähnlich zu dem OP:

struct inner { 
    int a; 
    int b; 
}; 

struct outer { 
    struct middle { 
     inner i; 
    }; 

    middle m; 
    int c; 
}; 

outer o = 
    {    // o { 
       // m { 
    inner{1,2}, //  i 
       // } 
    3   // c 
    };    // } 

anzumerken, dass spangen elision rekursiv gilt; wir können sogar die verwirrende

outer o = 
    {  // o { 
      // m { 
      //  i { 
    1,  //  a 
    2,  //  b 
      //  } 
      // } 
    3  // c 
    };  // } 

schreiben, wo wir beide die Zahnspange für o.m und o.m.i wegzulassen. Die ersten beiden Initialisierer-Klauseln werden verbraucht, um o.m.i zu initialisieren, der verbleibende initialisiert o.c. Sobald wir ein Paar von Klammern um 1,2 einsetzen, wird es als das Paar von Klammern interpretiert entsprechend o.m:

outer o = 
    {  // o { 
    {  // m { 
      //  i { 
     1, //  a 
     2, //  b 
      //  } 
    }  // } 
    3  // c 
    };  // } 

Hier wird der Initialisierer für o.m mit einem { beginnen, damit verspannen-elision keine Anwendung findet. Der Initialisierer für o.m.i ist 1, der nicht mit { beginnt, daher wird für o.m.i die Klammerelektion angewendet und die beiden Initialisierer 1 und 2 werden verbraucht.

+0

Schöne, umfassende Antwort. Vielen Dank! – Jeremy

+0

++ Abstimmung, ausgezeichnete Antwort wie üblich. Eine Frage allerdings: Es gibt bereits einen Post-N4527-Entwurf? – Columbo

+0

@Columbo Nun, nicht offiziell. Es stammt aus dem GitHub Repo, was es nicht zu einem offiziellen Arbeitsentwurf, sondern zu * vorläufigem Arbeitsmaterial * macht. – dyp

Verwandte Themen