2009-10-26 13 views
17

Dopo aver risposto alla domanda this stavo cercando di trovare il modello is_complete nella libreria Boost e mi sono reso conto che non esiste un modello di questo tipo in Boost.TypeTraits. Perché non esiste un modello simile nella libreria Boost? Come dovrebbe essere?Come scrivere il modello `is_complete`?

//! Check whether type complete 
template<typename T> 
struct is_complete 
{ 
    static const bool value = (sizeof(T) > 0); 
}; 

... 

// so I could use it in such a way 
BOOST_STATIC_ASSERT(boost::is_complete<T>::value); 

Il codice di cui sopra non è corretto, perché è illegale applicare sizeof a un tipo incompleto. Quale sarà una buona soluzione? È possibile applicare SFINAE in questo caso in qualche modo?


Beh, questo problema non poteva essere risolto in generale senza violare la ODR rule, ma c'è una specifica piattaforma solution che funziona per me.

+10

penso che questo non può funziona in linea di principio (tranne quando lo si applica sempre a un tipo che rimane sempre incompleto o è sempre completo). Perché se 'U' è completo o no,' is_complete 'specifica sempre lo stesso tipo. Se ora vai e usi 'is_complete ' in due diverse unità di traduzione, il membro del valore potrebbe avere un valore diverso ogni volta e il compilatore è libero in quale valore utilizza. Penso che questo non è valido, ma non riesco a trovare una dichiarazione dello Standard su questo :(Sarebbe felice se tu potessi scoprirlo. –

+0

La domanda più grande è perché. Senza riflessione perché hai bisogno di questo come il compilatore sa a tempo del compilatore –

+0

Anche in C++ 03 non è possibile eseguire questa operazione. Al massimo con C++ 0x con "sfinae for expressions", ma anche se si passa 'vector ' ad esempio e' vector' viene dichiarato solo ma non definito, quindi la verifica della completezza produrrà una istanziazione implicita e, se la definizione non è disponibile, genererà un errore grave non coperto da sfinae (l'errore non è nel "contesto immediato"). –

risposta

11

La risposta in Alexey Malistov può essere utilizzato su MSVC con una lieve modifica:

namespace 
{ 
    template<class T, int discriminator> 
    struct is_complete { 
     static T & getT(); 
     static char (& pass(T))[2]; 
     static char pass(...); 
     static const bool value = sizeof(pass(getT()))==2; 
    }; 
} 
#define IS_COMPLETE(X) is_complete<X,__COUNTER__>::value 

Sfortunatamente, il COUNTER macro predefinita non è parte dello standard, quindi non funzionerebbe sulla ogni compilatore.

+0

' __COUNTER__' è supportato da MSVC e con gcc a partire da 4.3. Tuttavia, con gcc, il problema non è proprio quello di creare diversi tipi 'is_complete' con l'aiuto di' __COUNTER__', ma fare in modo che il compilatore faccia SFINAE e selezioni il sovraccarico 'pass (...)'. –

+0

Questo non sembra essere compatibile con i tipi astratti completi. Su MSVC 2008 'IS_COMPLETE (MyAbstractClass)' non riesce a compilare. E anche se il compilatore ha permesso questo ed è stato in grado di fare il SFINAE con il tipo astratto, sembra che avrebbe dato la risposta sbagliata (cioè avrebbe riferito che un tipo astratto completo era in effetti incompleto). – bshields

+3

Questo viola la regola ODR. Il contatore ti offre più specializzazioni diverse in un'unità di traduzione, ma se la utilizzi in un secondo file, la stessa specializzazione otterrà una definizione incompatibile. La soluzione è metterlo in uno spazio dei nomi senza nome. – Potatoswatter

0

Non riesco a trovare nulla nello standard che garantisce che sizeof su un tipo incompleto produrrà 0. Richiede, tuttavia, che se T è incompleto ad un certo punto, ma completato in seguito in quell'unità di traduzione, che tutti i riferimenti a T si riferiscono allo stesso tipo - così come l'ho letto, anche se T è incompleto dove è stato richiamato il tuo modello, sarebbe necessario dire che era completo se T è stato completato da qualche parte in quell'unità di traduzione.

+0

"_che se T è incompleto a un certo punto, ma completato più tardi in quell'unità di traduzione, che tutti i riferimenti a T si riferiscono allo stesso tipo_" infatti, un tipo definito dall'utente non è ** intrinsecamente completo o incompleto **, è solo incompleto fino al completamento ('void' è intrinsecamente incompleto). Quindi l'intera idea è estremamente problematica. – curiousguy

+1

'sizeof' è mal formato per tipi incompleti e non può produrre zero, poiché C++ 98:" L'operatore 'sizeof' non deve essere applicato a un'espressione che ha ... tipo incompleto, ... o al nome tra parentesi di tale tipi ". Il trattamento di GCC non è conforme, ma è comunque una buona idea supportarlo in caso di scrittura di una simile struttura. – Potatoswatter

7
template<class T> 
struct is_complete { 
    static T & getT(); 
    static char (& pass(T))[2]; 
    static char pass(...); 

    static const bool value = sizeof(pass(getT()))==2; 
}; 
+4

Bello, ma come dice @litb nel suo commento, non funziona correttamente se is_complete appare in 2 posizioni contraddittorie nello stesso file, quando tra loro compare la definizione di tipo di classe (ho provato :)). – Asaf

+0

non riesce con errore: inizializzazione argomento 1 di 'static char (& is_complete :: pass (T)) [2] [con T = Foo]' –

+0

compilling con gcc 4.4.2 –

2

ho paura non si può attuare tali un is_complete tipo tratti. L'implementazione data dalla @Alexey non riesce a compilare il G ++ 4.4.2 e 4.5.0 G ++:

error: initializing argument 1 of ‘static char (& is_complete::pass(T))[2] [with T = Foo]’

Sul mio Mac, con il G ++ 4.0.1 valutare is_complete<Foo>::value dove struct Foo; è rese incomplete per true che è anche peggio di un errore del compilatore.

T può essere sia completo che incompleto nello stesso programma, a seconda dell'unità di traduzione ma è sempre lo stesso tipo. Di conseguenza, come commentato sopra, lo is_complete<T> è sempre lo stesso tipo.

Quindi, se si rispetta lo ODR non è possibile calcolare lo is_complete<T> con valori diversi a seconda di dove viene utilizzato; altrimenti significherebbe che hai diverse definizioni per is_complete<T> che ODR proibisce.

EDIT: come la risposta accettata, io stesso violato intorno ad una soluzione che utilizza il __COUNTER__ macro per creare un'istanza di un diverso tipo di is_complete<T, int> ogni volta la macro IS_COMPLETE viene utilizzato. Tuttavia, con gcc, non sono riuscito a far funzionare la SFINAE in primo luogo.

2

Risolvere questo richiede di eseguire il calcolo in argomento di default del modello tratto, come il tentativo di cambiare la definizione di un modello di viola la regola ODR (anche se una combinazione di __COUNTER__ e namespace {} possibile aggirare ODR).

Questo è scritto in C++ 11 ma può essere regolato per funzionare in modalità C++ 03 di un compilatore C++ 11 moderatamente recente.

template< typename t > 
typename std::enable_if< sizeof (t), std::true_type >::type 
is_complete_fn(t *); 

std::false_type is_complete_fn(...); 

template< typename t, bool value = decltype(is_complete_fn((t *) nullptr))::value > 
struct is_complete : std::integral_constant< bool, value > {}; 

Online demo.

L'argomento di default viene valutato in cui il modello si chiama, in modo che possa contestualmente passare tra diverse definizioni. Non c'è bisogno di una diversa specializzazione e definizione per ogni uso; ne hai solo bisogno uno per true e uno per false.

La regola è dato in §8.3.6/9, che vale anche per gli argomenti delle funzioni predefinite e di default template-argomenti:

Default arguments are evaluated each time the function is called.

ma attenzione, utilizzando questo all'interno di un modello è quasi sicuro di violare la ODR. Un modello istanziato su un tipo incompleto non deve fare nulla di diverso da se è stato istanziato su un tipo completo. Io personalmente voglio solo questo per un static_assert.

Per inciso, questo principio può anche essere utile se si desidera andare dall'altra parte e implement la funzionalità di __COUNTER__ utilizzando modelli e sovraccarico.

-1

miei 5 centesimi:

template <typename T, typename = void> 
    struct is_incomplete : ::std::true_type 
    { 
    }; 

    template <typename T> 
    struct is_incomplete<T, decltype(sizeof(T))> : ::std::false_type 
    { 
    }; 

    template <> 
    struct is_incomplete<void> : ::std::false_type 
    { 
    }; 
+0

Per funzionare correttamente, ho dovuto apportare alcune modifiche: 'namespace detail { template struct is_incomplete: :: std :: true_type {}; modello struct is_incomplete : :: std :: false_type {}; } modello struct is_incomplete: detail :: is_incomplete :: type {}; ' – ScootyPuff

+0

@ScootyPuff Apporta la modifica, non mi dispiace. – user1095108

+0

Questo ti dirà se la classe è incompleta al primo interrogativo, ma è mal formata (senza diagnostica e non funzionerà nella pratica) se la usi nuovamente dopo che la classe è stata definita. – Potatoswatter

5

potrebbe essere un po 'tardi, ma finora, nessuna soluzione C++ 11 hanno lavorato per entrambi i tipi completi e astratti.

Quindi eccovi.

Con VS2015 (V140), g ++> = 4.8.1, clang> = 3.4, questo funziona:

template <class T, class = void> 
struct IsComplete : std::false_type 
{}; 

template <class T> 
struct IsComplete< T, decltype(void(sizeof(T))) > : std::true_type 
{}; 

Grazie alla Bat-Ulzii Luvsanbat: https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/

Con VS2013 (V120) :

namespace Details 
{ 

    template <class T> 
    struct IsComplete 
    { 
     typedef char no; 
     struct yes { char dummy[2]; }; 

     template <class U, class = decltype(sizeof(std::declval<U>())) > 
     static yes check(U*); 

     template <class U> 
     static no check(...); 

     static const bool value = sizeof(check<T>(nullptr)) == sizeof(yes); 
    }; 

} // namespace Details 


template <class T> 
struct IsComplete : std::integral_constant< bool, Details::IsComplete<T>::value > 
{}; 

questo è ispirato dai internet e static assert that template typename T is NOT complete?

Problemi correlati