5

ho alcuni tipi che hanno sotto-tipi con lo stesso nome di ciascuno:compilazione personalizzato messaggio di errore quando sottotipo definito si accede

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

e anche i tipi che non hanno questo sottotipo, ma che vengono utilizzati nello stesso contesto:

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

Come è possibile aggiungere un sottotipo fittizio che fornisce un messaggio di errore di compilazione personalizzato?

mio (finora senza successo) tentativo è:

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

Ma static_assert(false, ...) non può funzionare, come il compilatore genera l'errore, anche se il tipo non è mai raggiungibile.

Come posso ritardare la valutazione di static_assert nel momento in cui si accede al tipo?

Un tentativo fallito è quello di introdurre un enum fittizio e costruire un'espressione fuori di esso:

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

caso d'uso Calcestruzzo: Ho una classe-template List che è definito con il sub- tipi head e tail se non vuoto, e dovrebbe dare un errore se vengono utilizzati questi sottotipi se vuota:

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

Se ho semplicemente omesso i tipi head e tail, quando accedo ad es. il 3 ° elemento di una lista che ha formato 2 con il codice List<int,int>::tail::tail::head dà il non così bel messaggio (g ++ 4.7.2): 'head' is not a member of 'List<int>::tail {aka List<>}'

+0

Questo esempio di 'Elenco <>' non si lamenta del 'static_assert's? Ho pensato che l'espressione costante fosse necessaria per coinvolgere un parametro di modello per evitare una valutazione immediata. – aschepler

+0

Hmm, il dummy enum [sembra non funzionare] (http://coliru.stacked-crooked.com/a/602ff84bdc70c08e). –

+0

@aschepler Funziona con g ++ 4.7.2, non è sicuro di altri compilatori o anche dello standard. – leemes

risposta

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: Questo problema tocca in realtà su tre aspetti di come i modelli C++ funzionano:

  1. (§14.7.1 [temp.inst]/p1) A meno che una specializzazione modello di classe è stato esplicitamente istanziato (14.7.2) o esplicitamente specializzato (14.7.3), la specializzazione del modello di classe è implicitamente istanziata quando la specializzazione è referenziata in un contesto che richiede un tipo di oggetto completamente definito o quando la completezza del tipo di classe influenza la semantica del programma.L'istanza implicita di una specializzazione del modello di classe causa l'istanza implicita delle dichiarazioni, ma non delle definizioni ... delle funzioni dei membri della classe, delle classi membro, [...].
  2. (§14.7.1 [temp.inst]/p11) Un'implementazione non deve istanziare implicitamente ... una classe membro ... di un modello di classe che non richiede l'istanziazione.
  3. (§14.6 [temp.res]/p8) Se non è possibile generare una specializzazione valida per un modello e tale modello non viene istanziato, il modello non è ben formato, non è richiesta alcuna diagnostica.

3) significa che l'espressione static_assert deve dipendere da un argomento di un template, altrimenti "non specializzazione valida" può essere generato per il modello e il programma è mal formato, e compilatori sono liberi di segnalare un errore (anche se non devono). Nel codice precedente è possibile generare una specializzazione valida per il primo modello, ma tale specializzazione non viene mai utilizzata a causa della specializzazione parziale.

La soluzione sopra riportata si basa anche su 1) e 2). 1) prevede che implicitamente un'istanza di un modello di specializzazione un'istanza solo i dichiarazioni (non definizioni) delle classi aderenti, e 2) significa che i compilatori sono affermativamente vietato tentare di istanziare head o tail se uno è semplicemente utilizzando un implicito istanziato List<> . Tieni presente che questa regola non si applica se instanzia esplicitamente List<> con template struct List<>;.

La soluzione in risposta leemes funziona perché typedef s non richiedono un tipo completo e quindi non attivano esemplificazione implicita di SubTypeErrorMessage<> al punto 1), e l'uso di un argomento modello nella static_assert in SubTypeErrorMessage bypassa 3), come è possibile generare una specializzazione valida (ovvero SubTypeErrorMessage<true>) per quel modello.

Vale la pena notare che in entrambi i casi le regole di istanziazione significano che è ancora legale utilizzare List<>::head o TypeC::subtype a condizione che non vengano utilizzati in un modo che richiede un tipo completo. Quindi qualcosa come int f(List<>::head &) { return 0; } è valido, anche se del tutto privo di significato poiché non è possibile in alcun modo che tu possa effettivamente chiamare quella funzione. Se non si specifica affatto List<>::head, tuttavia, il compilatore segnalerà un errore (forse criptico) su questo codice. Quindi questo è il compromesso per i messaggi di errore più carini :)

+0

Oh bello, c'è persino la "logica corretta" nell'espressione;) (anche se i casi con una dimensione maggiore 0 non lo raggiungono mai ...) Grazie per questa bella soluzione. – leemes

+0

Grazie mille per la tua spiegazione dettagliata. Quindi, riassumendo, sia la tua sia la mia soluzione sono garantite per funzionare, cioè il compilatore non è autorizzato a valutare l'asserzione prima di quanto mi piacerebbe che fosse? – leemes

+0

@leemes credo di si. –

5

Per ritardare la valutazione del static_assert al punto in cui si accede il tipo, è necessario rendere l'espressione dipende da un parametro modello.

Una possibile soluzione è quella di aggiungere un modello classe di supporto solo per la stampa il messaggio di errore condizionale (a seconda del valore del parametro di template):

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

Poi, nel tipo di cemento in cui si desidera avere una "fittizia sottotipo":

struct TypeC { 
    typedef SubTypeErrorMessage<> subtype; 
}; 

Live Demo

Problemi correlati