2015-01-27 8 views
8

Ist es möglich zu überprüfen, ob ein Vorlagetyp zur Kompilierzeit instanziiert wurde, damit ich diese Informationen in einer enable_if Spezialisierung verwenden kann?Kompilierzeit Vorlage Instanziierungsüberprüfung

Lassen Sie uns sagen, ich habe

template <typename T> struct known_type { }; 

Kann ich irgendwie etwas is_known_type definieren, deren Wert true, wenn known_type bei der Kompilierung instanziiert wird?

+0

Könnten Sie bitte ein Beispiel mit dieser Art von Informationen in einem enable_if zeigen? – erenon

+0

Die angenommene Antwort scheint auf neueren Compilern nicht zu funktionieren. Es scheint, dass die Antwort von einigen GCC-Compiler-Fehlern abhängt und die Lösung nicht mit standardkonformen Compilern funktioniert. –

Antwort

15

Es ist möglich, dies zu tun, wenn Sie die Tatsache nutzen, dass bestimmte Ausdrücke an Stellen verwendet werden, an denen constexpr erwartet werden, und dass Sie abfragen können, wie der Status für jeden Kandidaten ist. Speziell in unserem Fall ist die Tatsache, dass constexpr s ohne Definition nicht als konstante Ausdrücke übergeben werden können und noexcept eine Garantie für konstante Ausdrücke ist. Daher gibt noexcept(...), das true zurückgibt, das Vorhandensein eines ordnungsgemäß definierten constexpr an.

Im Wesentlichen behandelt dies constexpr s als Ja/Nein-Schalter und führt den Status zur Kompilierzeit ein.

Beachten Sie, dass dies ein Hack ist, benötigen Sie Workarounds für bestimmte Compiler (siehe die Artikel voraus) und diese spezifische friend-basierte Implementierung könnte als fehlerhaft durch zukünftige Überarbeitungen des Standards betrachtet werden.

Mit diesem aus dem Weg ...

Benutzer Filip Roséen stellt dieses Konzept in his article es speziell gewidmet ist.

Seine Beispiel-Implementierung ist, mit zitierte Erklärungen:

constexpr int flag (int); 

A constexpr Funktion in einem von zwei Zuständen sein kann; Entweder ist es verwendbar in einem konstanten Ausdruck, oder es ist nicht - wenn es eine Definition fehlt, fällt es automatisch in die letztere Kategorie - es gibt keinen anderen Staat (außer wir betrachten undefiniertes Verhalten).

Normalerweise sollten conexpr Funktionen genau so behandelt werden, wie sie sind; Funktionen, aber wir können sie auch als individuelle Handles zu "Variablen" mit einem Typ ähnlich wie Bool, wo jede "Variable" kann einen von zwei Werten haben; verwendbar oder nicht verwendbar.

In unserem Programm hilft es, wenn Sie die Flagge als genau das betrachten; ein Handle (keine Funktion). Der Grund dafür ist, dass wir das Flag niemals in einem evaluierten Kontext aufrufen werden, wir sind nur an seinem aktuellen Zustand interessiert.

template<class Tag> 
struct writer { 
    friend constexpr int flag (Tag) { 
    return 0; 
    } 
}; 

writer ist eine Klasse-Vorlage, die bei der Instanziierung, eine Definition für eine Funktion in dem umgebenden Namensraum (mit der Signatur int-Flag (Tag) schaffen, in dem ein Tag ist template- Parameter).

Wenn wir wieder einmal von constexpr Funktionen denken als bis zu einem gewissen Variable behandelt, können wir eine Instanziierung des Schriftstellers als unbedingtes Schreiben des Wertes verwendbar auf die Variable hinter der Funktion in der Freund-Erklärung behandeln.

template<bool B, class Tag = int> 
struct dependent_writer : writer<Tag> { }; 

wäre ich nicht überrascht, wenn Sie denken, dependent_writer wie ein ziemlich sinnlos indirection aussieht; Warum nicht direkt instantiieren Schriftsteller , wo wir es verwenden möchten, anstatt durch dependent_writer gehen?

  1. Instantiierung des Schreibers muss von etwas abhängen, um sofortige Instanziierung zu verhindern, und;
  2. dependent_writer wird in einem Kontext verwendet, in dem ein Wert vom Typ bool als Abhängigkeit verwendet werden kann.
template< 
    bool B = noexcept (flag (0)), 
    int = sizeof (dependent_writer<B>) 
> 
constexpr int f() { 
    return B; 
} 

Die oben vielleicht ein wenig seltsam aussehen, aber es ist wirklich ganz einfach;

  1. setzt B = wahr, wenn Flag (0) ein konstanter Ausdruck ist, andernfalls B = false, und;
  2. implizit instanziiert instant_writer (sizeof benötigt einen vollständig definierten Typ).

Das Verhalten kann mit dem folgenden Pseudocode ausgedrückt werden:

IF [ `int flag (int)` has not yet been defined ]: 
    SET `B` = `false` 
    INSTANTIATE `dependent_writer<false>` 
    RETURN  `false` 
ELSE: 
    SET `B` = `true` 
    INSTANTIATE `dependent_writer<true>` 
    RETURN  `true` 

Schließlich ist der Proof of Concept:

int main() { 
    constexpr int a = f(); 
    constexpr int b = f(); 
    static_assert (a != b, "fail"); 
} 

ich dies auf Ihre Anwendung Problem. Die Idee ist, die constexpr Ja/Nein-Schalter zu verwenden, um anzuzeigen, ob ein Typ instanziiert wurde. Sie benötigen also einen separaten Schalter für jeden Typ, den Sie haben.

template<typename T> 
struct inst_check_wrapper 
{ 
    friend constexpr int inst_flag(inst_check_wrapper<T>); 
}; 

inst_check_wrapper<T> wickelt im Wesentlichen einen Schalter für wie auch immer geartete Sie es geben kann. Es ist nur eine generische Version des ursprünglichen Beispiels.

template<typename T> 
struct writer 
{ 
    friend constexpr int inst_flag(inst_check_wrapper<T>) 
    { 
     return 0; 
    } 
}; 

Der Switch-Toggler ist identisch mit dem im ursprünglichen Beispiel. Es kommt mit der Definition für den Schalter eines Typs, den Sie verwenden.Um eine einfache Überprüfung zu ermöglichen, fügen Sie einen Helper Switch Inspector hinzu:

template <typename T, bool B = noexcept(inst_flag(inst_check_wrapper<T>()))> 
constexpr bool is_instantiated() 
{ 
    return B; 
} 

Schließlich "registriert" sich der Typ selbst als initialisiert. In meinem Fall:

template <typename T> 
struct MyStruct 
{ 
    template <typename T1 = T, int = sizeof(writer<MyStruct<T1>>)> 
    MyStruct() 
    {} 
}; 

Der Schalter wird eingeschaltet, sobald dieser bestimmte Konstruktor gefragt wird. Beispiel:

int main() 
{ 
    static_assert(!is_instantiated<MyStruct<int>>(), "failure"); 
    MyStruct<int> a; 
    static_assert(is_instantiated<MyStruct<int>>(), "failure"); 
} 

Live on Coliru.

+0

Ich fürchte, das wird nicht funktionieren mehrere Übersetzungseinheiten. –

+0

Soll das "schwarze Magie" bedeuten? –

+1

@GuillaumeRacicot es ist Templetemancy. –

-1

Es gibt keine Möglichkeit, das zu tun. Also würde ich sagen: Nein.

2

Nein, eine Kompilierzeitprüfung für nicht instanziierte Klassen ist nicht möglich. Sie können jedoch eine (statische) Zuordnung von instanziierten Klassen (im Debug-Build) erstellen, die Sie zur Laufzeit überprüfen können.

Das Analysieren der verknüpften Binärdatei durch Vergleichen einer Liste erwarteter instanziierter Klassen mit tatsächlich instanziierten Klassen sollte jedoch möglich sein (aber das ist nach der Kompilierzeit und nach meinem Wissen).

+0

mein Problem löst überlappende partielle Spezialisierungen einer Struktur, ich habe eine Spezialisierung, die nur für bekannte Typen angewendet werden sollte, die nicht wirklich bekannt sind, bis Kompilierzeit es eine Vorlage Bibliothek bin ich schreibe – tuccio