2012-09-11 4 views
19

Ho utilizzato il codice da "Is there a way to test whether a C++ class has a default constructor (other than compiler-provided type traits)?".Perché utilizzare due sizeofs per verificare se una classe è configurabile come predefinita, ma non è possibile?

ho modificato leggermente a lavorare con tutti i miei casi di test:

template< class T > 
class is_default_constructible { 
    typedef int yes; 
    typedef char no; 


    // the second version does not work 
#if 1 
    template<int x, int y> class is_equal {}; 
    template<int x> class is_equal<x,x> { typedef void type; }; 

    template< class U > 
    static yes sfinae(typename is_equal< sizeof U(), sizeof U() >::type *); 
#else 
    template<int x> class is_okay { typedef void type; }; 

    template< class U > 
    static yes sfinae(typename is_okay< sizeof U() >::type *); 
#endif 

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

public: 
    enum { value = sizeof(sfinae<T>(0)) == sizeof(yes) }; 
}; 

Perché funziona correttamente con la versione lite due template, ma non con quella normale (set #if 0)? Si tratta di un bug del compilatore? Sto usando Visual Studio 2010.

ho usato le seguenti prove:

BOOST_STATIC_ASSERT(is_default_constructible<int>::value); 
BOOST_STATIC_ASSERT(is_default_constructible<bool>::value); 
BOOST_STATIC_ASSERT(is_default_constructible<std::string>::value); 
BOOST_STATIC_ASSERT(!is_default_constructible<int[100]>::value); 

BOOST_STATIC_ASSERT(is_default_constructible<const std::string>::value); 

struct NotDefaultConstructible { 
    const int x; 
    NotDefaultConstructible(int a) : x(a) {} 
}; 

BOOST_STATIC_ASSERT(!is_default_constructible<NotDefaultConstructible>::value); 

struct DefaultConstructible { 
    const int x; 

    DefaultConstructible() : x(0) {} 
}; 

BOOST_STATIC_ASSERT(is_default_constructible<DefaultConstructible>::value); 

Sono davvero ad una perdita qui:

  1. due test non riescono con l'altra versione: int[100] e NotDefaultConstructible. Tutti i test hanno esito positivo con la versione di due argomenti del modello.
  2. Visual Studio 2010 non supporta std::is_default_constructible. Tuttavia, la mia domanda riguarda il motivo per cui c'è qualche differenza nelle due implementazioni e perché uno lavora e l'altro no.
+3

Perché non verificare se hai già nella libreria standard [ 'std :: is_default_constructible'] (http://en.cppreference.com/w/cpp/types/is_default_constructible)? –

+2

Che cosa non funziona? Funziona bene su g ++, tranne per 'BOOST_STATIC_ASSERT (! Is_default_constructible :: value);' –

+0

@ BЈовић Se un assert non funziona, e che asserire è corretto, non funziona bene, sicuramente? – hvd

risposta

3

(La mia risposta è molto informato dalla risposta precedente di DS.)

Prima di tutto, si noti che avete class is_okay { typedef void type; }, vale a dire, type è un membro privato di is_okay. Ciò significa che non è effettivamente visibile al di fuori della classe e pertanto

template< class U > 
static yes sfinae(typename is_equal< sizeof U(), sizeof U() >::type *); 

non avrà mai successo. Tuttavia, SFINAE non si applicava originariamente a questa situazione in C++ 98; non è stato fino alla risoluzione della DR 1170 che "il controllo dell'accesso [iniziato] è stato fatto come parte del processo di sostituzione". [1]

(Sorprendentemente, Paolo Carlini ha scritto che blog appena 10 giorni fa, quindi il vostro tempismo con questa domanda è impeccabile. In casi come questo, secondo Carlini, GCC prima del 4.8 non ha effettuato alcun controllo dell'accesso durante la SFINAE. Questo spiega perché non hai visto GCC lamentarsi della privatezza di type. Dovresti essere utilizzando un GCC top-of-albero dal letteralmente a meno di due settimane fa, per vedere il comportamento corretto.)

Clang (top-of-albero) segue il DR in -std=c++11 modalità, ma fornisce l'errore previsto nella sua modalità C++ 03 predefinita (cioè Clang non segue il DR in modalità C++ 03). Questo è un po 'strano, ma forse lo fanno per la compatibilità all'indietro.

Ma in ogni caso, in realtà non si desidera che lo type sia privato in primo luogo. Quello che intendevi scrivere è struct is_equal e struct is_okay.

Con questa modifica, Clang supera tutti i casi di test. GCC 4.6.1 passa anche tutti i casi di test, eccetto per int[100]. GCC pensa che int[100] sia ok, mentre stai affermando che è non okay.

Ma un altro problema con il codice è che non verifica quello che pensi che stia testando. Il C++ standard clausola 8.5 # 10, dice chiaramente: [2]

Un oggetto le cui inizializzatore è un insieme vuoto di parentesi, cioè (), sarà valore-inizializzato.

Quindi, quando si scrive sizeof U(), non sei testando se U può essere predefinita -initialized; stai testando se può essere valore -inizializzato!

... o sei tu? Almeno in alcuni dei miei casi di test, i messaggi di errore di GCC indicavano che U() veniva interpretato come il nome di un tipo - "funzione che restituisce U" - e che era il motivo per cui int[100] si comportava in modo diverso. Non vedo come questo comportamento sia valido, ma davvero non capisco le sottigliezze sintattiche qui.

Se davvero intenzione di testare predefinita l'inizializzazione, si dovrebbe usare qualcosa di simile in tutto il mondo sizeof *new U attualmente avete sizeof U().

A proposito, int[100]è default-initializable, punto. Lo standard è chiaro su cosa significa per default: inizializzare un tipo di array.

Infine, mi chiedo se una delle cause di un comportamento stravagante nel codice è che si sta cercando di passare un disadorno 0 (che è di tipo int) ad una funzione il cui insieme di sovraccarichi include una funzione di prendere void * ed una presa .... Potrei capire completamente se un compilatore ha scelto quello sbagliato in quel caso. Ti consigliamo di provare a passare 0 a una funzione che richiede int.

Mettendo tutto insieme, ecco una versione del tuo codice che funziona perfettamente per me (cioè, nessun errore di asserzione) in ToT Clang e GCC 4.6.1.

template< class T > 
class is_default_constructible { 
    typedef int yes; 
    typedef char no; 

    template<int x> struct is_okay { typedef int type; }; 

    template< class U > 
    static yes sfinae(typename is_okay< sizeof (*new U) >::type); 

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

public: 
    enum { value = sizeof(sfinae<T>(0)) == sizeof(yes) }; 
}; 

#if __has_feature(cxx_static_assert) 
#define BOOST_STATIC_ASSERT(x) static_assert(x, "or fail") 
#else 
#define dummy2(line) dummy ## line 
#define dummy(line) dummy2(line) 
#define BOOST_STATIC_ASSERT(x) int dummy(__COUNTER__)[(x) - 1] 
#endif 

#include <string> 

BOOST_STATIC_ASSERT(!is_default_constructible<int()>::value); 
BOOST_STATIC_ASSERT(is_default_constructible<bool>::value); 
BOOST_STATIC_ASSERT(is_default_constructible<std::string>::value); 
BOOST_STATIC_ASSERT(is_default_constructible<int[100]>::value); 

BOOST_STATIC_ASSERT(is_default_constructible<const std::string>::value); 

struct NotDefaultConstructible { 
    const int x; 
    NotDefaultConstructible(int a) : x(a) {} 
}; 

BOOST_STATIC_ASSERT(!is_default_constructible<NotDefaultConstructible>::value); 

struct DefaultConstructible { 
    const int x; 

    DefaultConstructible() : x(0) {} 
}; 

BOOST_STATIC_ASSERT(is_default_constructible<DefaultConstructible>::value); 
+0

Lo hai spiegato molto bene. Ho mirato a fallire su 'int [100]', perché ho scritto una funzione template con un parametro opzionale 'T defaultValue = T()', che non ha funzionato per 'T = int [100]' in VS2010. Fino ad ora non conoscevo l'inizializzazione default- vs valore. – BlackHC

3

Questo sembra quasi certamente un artefatto (bug) del compilatore, dal momento che g ++ si comporta (e fallisce) in modo diverso. Posso solo immaginare il motivo per cui VS comporta in modo diverso, ma una congettura che sembra ragionevole è che questa classe:

template<int x> class is_okay { typedef void type; }; 

ha la stessa definizione a prescindere dal parametro di template, quindi forse il compilatore salta un passo quando si analizzano static sfinae(typename is_okay< sizeof U() >::type *); e considera è ben definito senza guardare da vicino il parametro di is_okay. Quindi pensa che tutto sia predefinito-costruibile.

Perché né VS né g ++ sono infastiditi dal fatto che is_okay::type sia privato, non lo so. Sembra che dovrebbero essere entrambi.

g ++, d'altra parte, tratta entrambe le versioni come equivalenti. In entrambi, tuttavia, genera un errore diverso per int[100]. Questo è discutibile sul fatto che dovrebbe essere costruttivamente predefinito. Sembri pensare che non dovrebbe essere. g ++ 47's std::is_default_constructible pensa che lo sia! Per ottenere questo comportamento (che è probabilmente più standard), è possibile sostituire T con typename boost::remove_all_extents<T>::type nella riga enum.