5

ich einige Typen haben, die mit dem gleichen Namen Untertypen haben jeweils:Benutzerdefinierte Fehlermeldung kompilieren, wenn undefinierte Subtyp zugegriffen wird

struct TypeA { 
    typedef int subtype; 
}; 
struct TypeB { 
    typedef float subtype; 
}; 

und auch Arten, die nicht diesen Subtyp aufweisen, aber verwendet werden, im gleichen Kontext:

struct TypeC { 
    // (no subtype defined) 
}; 

Wie kann ich einen Dummy-Untertyp hinzufügen, der eine benutzerdefinierte Kompilierfehlermeldung gibt?

Meine (bisher erfolglos) versucht wird:

struct TypeC { 
    struct subtype { 
     static_assert(false, "Attempt to access the non-existent subtype of TypeC."); 
    }; 
}; 

Aber static_assert(false, ...) kann nicht funktionieren, da der Compiler den Fehler, auch wenn der Typ wirft nie abgerufen.

Wie kann ich die Auswertung von static_assert auf den Zeitpunkt verzögern, zu dem auf den Typ zugegriffen wird?

einem gescheiterten Versuch ist ein Dummy enum einzuführen und einen Ausdruck aus ihm zu konstruieren:

enum { X }; 
static_assert(X != X, "..."); 

Konkrete Anwendungsfall: Ich habe eine Klasse-template List, die mit dem sub- definiert ist Typen head und tail wenn nicht leer ist, und sollte einen Fehler, wenn diese Untertypen verwendet werden geben, wenn es leer ist:

template<typename...> 
struct List; 

// empty list: 
template<> 
struct List<> { 
    struct head { static_assert(false, "Attempt to access the head of an empty list."); }; 
    struct tail { static_assert(false, "Attempt to access the tail of an empty list."); }; 
}; 

// non-empty list: 
template<typename Head, typename ...Tail> 
struct List<Head, Tail...> { 
    typedef Head head; 
    typedef List<Tail...> tail; 
}; 

Wenn ich einfach die Typen head und tail weglassen, wenn z. das dritte Element einer Liste, die Größe 2 mit dem Code hat List<int,int>::tail::tail::head gibt das nicht so nette Nachricht (g ++ 4.7.2): 'head' is not a member of 'List<int>::tail {aka List<>}'

+0

Das 'List <>' Beispiel beschwert sich nicht über die 'static_assert's? Ich dachte, der konstante Ausdruck erfordere einen Template-Parameter, um eine sofortige Auswertung zu vermeiden. – aschepler

+0

Hmm, die Dummy-Enum [scheint nicht zu funktionieren] (http://coliru.stacked-crooked.com/a/602ff84bdc70c08e) entweder. –

+0

@aschepler Es funktioniert mit g ++ 4.7.2, nicht sicher über andere Compiler oder sogar den Standard. – leemes

Antwort

5
// empty list: 
template<typename... Args> 
struct List { 
    struct head {static_assert(sizeof...(Args) != 0, "Attempt to access the head of an empty list."); }; 
    struct tail {static_assert(sizeof...(Args) != 0, "Attempt to access the tail of an empty list."); }; 
}; 

// non-empty list: 
template<typename Head, typename ...Tail> 
struct List<Head, Tail...> { 
    typedef Head head; 
    typedef List<Tail...> tail; 
}; 

Edit: Das Problem tatsächlich berührt drei Aspekte, wie C++ Vorlagen arbeiten:

  1. (§14.7.1 [temp.inst]/p1) Sofern eine Klasse Template-Spezialisierung explizit instanziiert wurde (14.7.2) oder explizit spezialisiert (14.7.3), wird die Klassenvorlagenspezialisierung implizit instanziiert, wenn auf die Spezialisierung in einem Kontext verwiesen wird, der einen vollständig definierten Objekttyp erfordert oder wenn die Vollständigkeit der Klassenart die Semantik des Programms beeinflusst.Die implizite Instanziierung einer Klassenvorlagen - Spezialisierung bewirkt die implizite Instanziierung der Deklarationen, aber nicht der Definitionen ... der Klassenmemberfunktionen, Mitgliedsklassen, [...].
  2. (§14.7.1 [temp.inst]/p11) Eine Implementierung soll nicht implizit ... eine Member-Klasse ... einer Klassenvorlage instantiieren, die keine Instanziierung erfordert.
  3. (§14.6 [temp.res]/p8) Wenn für eine Vorlage keine gültige Spezialisierung generiert werden kann und diese Vorlage nicht instanziiert wird, ist die Vorlage fehlerhaft, keine Diagnose erforderlich.

3) bedeutet, dass der static_assert Ausdruck auf einem Template-Argumente abhängen muss, da sonst „keine gültige Spezialisierung“ kann für die Vorlage und das Programm erzeugt werden, schlecht ausgebildet und Compiler sind frei, einen Bericht zu erstatten Fehler (obwohl sie nicht müssen). In dem obigen Code kann eine gültige Spezialisierung für die erste Vorlage generiert werden, aber eine solche Spezialisierung wird aufgrund der teilweisen Spezialisierung nie verwendet.

Die oben angegebene Lösung beruht ebenfalls auf 1) und 2). 1) sieht vor, dass implizit Instanziieren eine Vorlage Spezialisierung instanziiert nur die Erklärungen (nicht Definitionen) der Mitgliedsklassen, und 2) bedeutet, dass Compilern bejahend aus dem Versuch ist verboten head oder tail wenn man instanziiert wird lediglich unter Verwendung eines instanziierten implizit List<> . Beachten Sie, dass diese Regel nicht gilt, wenn Sie List<> mit template struct List<>; explizit instanziieren.

Die Lösung in leemes Antwort funktioniert, weil typedef s keine vollständige Art erfordern und so löst keine implizite Instanziierung SubTypeErrorMessage<> unter 1), und die Verwendung eines Template-Arguments in den static_assert in SubTypeErrorMessage umgeht 3), einen Eine gültige Spezialisierung (zB SubTypeErrorMessage<true>) kann für diese Vorlage generiert werden.

Es ist erwähnenswert, dass in beiden Fällen die Instanziierungsregeln bedeuten, dass es immer noch zulässig ist, List<>::head oder TypeC::subtype zu verwenden, solange Sie sie nicht in einer Weise verwenden, die einen vollständigen Typ erfordert. Daher ist etwas wie int f(List<>::head &) { return 0; } gültig, obwohl es völlig bedeutungslos ist, da es keine Möglichkeit gibt, diese Funktion tatsächlich aufzurufen. Wenn Sie jedoch List<>::head überhaupt nicht definieren, meldet der Compiler einen (möglicherweise kryptischen) Fehler in diesem Code. Also das ist der Kompromiss für schönere Fehlermeldungen :)

+0

Oh netter, da gibt es sogar die "richtige Logik" im Ausdruck;) (obwohl Fälle mit einer Größer größer als 0 es nie erreichen ...) Danke Sie für diese nette Lösung. – leemes

+0

Vielen Dank für Ihre ausführliche Erklärung. Zusammenfassend kann gesagt werden, dass sowohl Ihre als auch meine Lösung funktionieren, d. H. Der Compiler darf die Assertion nicht früher auswerten, als ich es möchte. – leemes

+0

@leemes Ich glaube schon. –

5

die Auswertung von static_assert auf den Punkt zu verzögern, wo Ihre Art zugegriffen wird, müssen Sie Machen Sie den Ausdruck abhängig von einem Vorlagenparameter.

Eine mögliche Lösung ist eine Hilfsklasse-Vorlage für den Druck der Fehlermeldung bedingt nur hinzufügen (je nach dem Wert der Template-Parameter):

template<bool X = false> 
struct SubTypeErrorMessage { 
    static_assert(X, "Attempt to access the non-existent subtype of TypeC."); 
}; 

Dann wird in dem konkreten Typ, wo Sie wollen ein "Dummy-Untertyp":

struct TypeC { 
    typedef SubTypeErrorMessage<> subtype; 
}; 

Live Demo