2015-06-03 8 views
7

Ich möchte das Union-Element im Konstruktor basierend auf einem Argument initialisiert auswählen. Im Folgenden ist ein Beispiel, das funktioniert:C++ 14 constexpr union konditionale Initialisierung in Konstruktor

struct A { 
    union { 
     int i; 
     float f; 
    }; 
    A(double d, bool isint) { 
     if (isint) new(&i) int(d); 
     else new(&f) float(d); 
    } 
}; 

Während ich int und float verwenden, das Ziel ist, mit anderen komplexeren Typen (aber immer noch zulässige in einer C++ 14 union) zu arbeiten, damit die Verwendung von Placement-Neu (und keine Aufgabe).

Das Problem ist, dass dieser Konstruktor nicht constexpr als Placement-Neu ist in constexpr Methoden nicht zulässig ist. Gibt es einen Weg dazu (abgesehen davon, dass das Argument isint zum formalen System gehört)? Irgendeine Art von konditionalen Initialisierungsliste würde funktionieren, aber ich bin mir nicht bewusst, wie ich das machen könnte.

+1

IIRC, Typ Punning ist (im Allgemeinen) innerhalb konstanter Ausdrücke verboten. Sie können beispielsweise alle Mitglieder separat speichern ("struct" statt "union") und die Kapselung verwenden, um zu erzwingen, dass nur eines der Mitglieder aktiv ist. – dyp

+0

Ich denke, es hängt davon ab, welche Art von Typ Punning Sie in Betracht ziehen. Ich kann sicherlich einen Konstruktor erklären, der 'i' initialisiert und einen anderen, der' f' initialisiert. Nur ein Mitglied der Gewerkschaft kann zu irgendeinem Zeitpunkt "aktiv" sein, aber ich versuche nicht, dies zu entfernen. Ich würde gerne dem Compiler mitteilen, welches Mitglied aktiv ist. – cshelton

+3

Sie haben Recht. Diese Art der Verwendung von Placement-Neu umgeht das Typsystem nicht wirklich. - Es ist möglich, dies in einer externen Funktion, z. 'constexpr A make_A (doppelter d, bool isint) {if (isint) return A (d, true_type {}); sonst gib zurück A (d, false_type {}); } '. Es sollte möglich sein, eine solche Funktion innerhalb des Konstruktors aufzurufen, wenn Sie stattdessen nicht anonyme Verbindungen verwenden.Etwas wie 'struct A {union storage {int i; float f; Speicher (double d, true_type); Speicher (double d, false_type); } m; A (double d, bool isint): m (make_storage (d, isint)) {}}; ' – dyp

Antwort

6

Es gibt einen Trick. Die Schlüsselelemente sind:

  1. Ein vordefinierter Kopier- oder Verschiebungskonstruktor für eine Union kopiert die Objektdarstellung (und kopiert damit das aktive Element) und ist bei der Auswertung von konstanten Ausdrücken zulässig.
  2. Sie können den aktiven Union-Member in einer konstanten Ausdruck Auswertung nicht ändern, nachdem die Initialisierung abgeschlossen ist, aber Sie können an einen anderen Konstruktor delegieren, um die Auswahl zu verzögern.
  3. Wenn Sie an den Konstruktor für Kopieren oder Verschieben delegieren, können Sie ein anderes Objekt, das bereits initialisiert wurde, in den richtigen Status übergeben und dessen aktives Mitglied kopieren.

Putting dies zusammen, erhalten wir:

template<typename T> struct tag {}; 
struct A { 
    union { 
    int i; 
    float f; 
    }; 
    constexpr A(tag<int>, double d) : i(d) {} 
    constexpr A(tag<float>, double d) : f(d) {} 
    constexpr A(double d, bool isint) : A(isint ? A(tag<int>(), d) : A(tag<float>(), d)) {} 
}; 

constexpr A a(1.0, true); // ok, initializes 'i' 
constexpr A b(5, false); // ok, initializes 'f' 

Das durch die jüngsten Clang akzeptiert wird, GCC, und EDG und erfordert nur C++ 11 constexpr.

Warnung: GCC 5.1.0 einen Fehler hatte, wo sie den obigen Code miscompiled (Initialisieren a und b-0); Dieser Fehler ist in früheren oder späteren Versionen von GCC nicht vorhanden.

+0

Funktioniert das, wenn die Mitglieder der Gewerkschaft nicht trivial sind? Ich denke nicht, dass es so ist. Zum Beispiel sollte ich dies mit einer Klasse tun können, die keinen Kopierkonstruktor hat. Aber ich glaube nicht, dass dies möglich ist. – cshelton

+0

@cshelton Ja, diese Methode ist kugelsicher. Der Elementinitialisierer 'i (d)' erstellt das Element "an Ort und Stelle". – Potatoswatter

+1

Der Dispatch-Konstruktor von 'A' move - konstruiert die' A' -Instanz aus einem 'A'-temporären; Diese Technik erfordert, dass die Konstruktion des Verschiebens (oder Kopierens) gültig ist, was wiederum erfordert, dass alle Vereinigungselemente des (Array-) Klassentyps triviale Verschiebungs- (oder Kopier-) Konstruktoren haben. Ich kenne keine Möglichkeit, diese Einschränkung zu umgehen. –

1

Für trivial konstruierbare Objekte ist new nicht erforderlich. Sie können die Lebensdauer des Objekts beginnen und das aktive Element union einfach durch den Zuweisungsoperator auswählen.

struct A { 
    union { 
     int i; 
     float f; 
    }; 
    A(double d, bool isint) { 
     if (isint) i = d; 
     else f = d; 
    } 
}; 

Wenn es wirklich ein Konstruktor irgendwo in dem Element, dann ist es notwendig, Richard Antwort zu verwenden.

+0

Zu meiner Lektüre wurde nach einem 'constexpr'-Konstruktor für' A' gefragt. Sie können hier nicht den Konstruktor "constexpr" erstellen, da es nicht möglich ist, das aktive Mitglied einer Union während der Auswertung eines konstanten Ausdrucks zu ändern. –

+0

@RichardSmith Ja, du hast Recht. Clang und GCC scheinen beide Ecken und Kanten zu haben und diese Regel etwas zu lockern. GCC ermöglicht das Ändern des aktiven Mitglieds. (Beide lehnen dieses Beispiel ab, wenn "constexpr" hinzugefügt wird.) – Potatoswatter

+0

@RichardSmith Es macht nichts, Clang ist konform :). – Potatoswatter