2012-04-19 11 views
12

La seguente espressione utilizzando is_assignable rendimenti true quando si utilizza gcc 4.7 e aumentare 1.49:risultati imprevisti quando si utilizza std :: is_assignable, boost :: funzione e nullptr

typedef boost::function<void()> F; 
std::is_assignable<F, std::nullptr_t>::value 

Tuttavia, questo codice non riesce a compilare:

boost::function<void()> f; 
f = nullptr; 

produttrici di questi messaggi di errore:

In file included from c:\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.0/../../../../include/boost/function/detail/maybe_include.hpp:13:0, 
      from c:\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.0/../../../../include/boost/function/detail/function_iterate.hpp:14, 
      from c:\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.0/../../../../include/boost/preprocessor/iteration/detail/iter/forward1.hpp:47, 
      from c:\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.0/../../../../include/boost/function.hpp:64, 
      from ..\main.cpp:8: 
c:\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.0/../../../../include/boost/function/function_template.hpp: In instantiation of 'static void boost::detail::function::void_function_obj_invoker0<FunctionObj, R>::invoke(boost::detail::function::function_buffer&) [with FunctionObj = std::nullptr_t; R = void]': 
c:\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.0/../../../../include/boost/function/function_template.hpp:907:60: required from 'void boost::function0<R>::assign_to(Functor) [with Functor = std::nullptr_t; R = void]' 
c:\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.0/../../../../include/boost/function/function_template.hpp:722:7: required from 'boost::function0<R>::function0(Functor, typename boost::enable_if_c<boost::type_traits::ice_not<boost::is_integral<Functor>::value>::value, int>::type) [with Functor = std::nullptr_t; R = void; typename boost::enable_if_c<boost::type_traits::ice_not<boost::is_integral<Functor>::value>::value, int>::type = int]' 
c:\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.0/../../../../include/boost/function/function_template.hpp:1042:16: required from 'boost::function<R()>::function(Functor, typename boost::enable_if_c<boost::type_traits::ice_not<boost::is_integral<Functor>::value>::value, int>::type) [with Functor = std::nullptr_t; R = void; typename boost::enable_if_c<boost::type_traits::ice_not<boost::is_integral<Functor>::value>::value, int>::type = int]' 
c:\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.0/../../../../include/boost/function/function_template.hpp:1083:5: required from 'typename boost::enable_if_c<boost::type_traits::ice_not<boost::is_integral<Functor>::value>::value, boost::function<R()>&>::type boost::function<R()>::operator=(Functor) [with Functor = std::nullptr_t; R = void; typename boost::enable_if_c<boost::type_traits::ice_not<boost::is_integral<Functor>::value>::value, boost::function<R()>&>::type = boost::function<void()>&]' 
..\main.cpp:172:6: required from here 
c:\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.0/../../../../include/boost/function/function_template.hpp:153:11: error: '* f' cannot be used as a function 

Inoltre, questa espressione restituisce false:

typedef boost::function<void()> G; 
std::is_assignable<G, decltype(NULL)>::value 

ma questo codice si compila:

boost::function<void()> g; 
g = NULL; 

I risultati di is_assignable non sembrano riflettere correttamente la funzionalità di boost::function. Sto facendo qualcosa di sbagliato qui? (Sto avendo problemi a dare un senso ai messaggi di errore.)

Ho pensato che i tratti del tipo dovevano essere un modo affidabile per determinare la funzionalità delle classi utilizzate nei modelli. I caratteri tipografici forniti in C++ 11 sono semplicemente incompatibili con la funzione boost ::?


di dare a questo un po 'di contesto, ho lavorato su diversi progetti personali per familiarizzare meglio me stesso con le nuove caratteristiche di C++ 11. Per questo particolare progetto, sto tentando di creare una classe che memorizza una funzione chiamabile che può essere "disattivata". Questo è più o meno quello che sto cercando di fare:

template <typename F> 
class callable_function 
{ 
public: 
    callable_function(F func) : func_(func) 
    { 
     /* func_ is initially active */ 
    } 

    void call() 
    { 
     if (/* func_ is active */) func_(); 
    } 

    void deactivate() 
    { 
     /* set func_ to deactive */ 
    } 

private: 
    F func_; 
}; 

per i blocchi /* func_ is active */ e /* set func_ to deactive */, voglio fornire due implementazioni diverse che vengono selezionati in fase di compilazione a seconda delle proprietà di F. Se nullptr possono essere assegnati ad func_ e func_ può essere utilizzato in un contesto booleano, allora voglio utilizzare il seguente (che è ciò che viene selezionato per i puntatori funzione built-in e std::function):

template <typename F> 
class callable_function 
{ 
public: 
    callable_function(F func) : func_(func) {} 

    void call() 
    { 
     if (func_) func_(); 
    } 

    void deactivate() 
    { 
     func_ = nullptr; 
    } 

private: 
    F func_; 
}; 

If nullptr non può essere assegnato a func_, quindi voglio memorizzare un valore booleano aggiuntivo all'interno della classe che memorizza lo stato "attivo". Questa implementazione è selezionato per funtori e funzioni lambda:

template <typename F> 
class callable_function 
{ 
public: 
    callable_function(F func) : func_(func), active_(true) {} 

    void call() 
    { 
     if (active_) func_(); 
    } 

    void deactivate() 
    { 
     active_ = false; 
    } 

private: 
    F func_; 
    bool active_; 
}; 

Dal nullptr attualmente non possono essere assegnati a boost::function, mi sarei aspettato la seconda esecuzione a scelta. Tuttavia, poiché true restituisce true per e nullptr, viene invece selezionata la prima implementazione, che genera un errore di compilazione nella funzione deactivate.

+5

Nota a margine: qualsiasi motivo per non utilizzare 'std :: function' invece di' boost :: function'? –

+0

Mi piacerebbe essere in grado di gestire i casi più comuni, che credo avrebbero incluso i puntatori di funzioni, i funtori, le funzioni lambda, 'std :: function' e' boost :: function'. La mia versione attuale funziona per tutto eccetto per 'boost :: function', a causa del problema discusso sopra. Dato che questo è solo un progetto di apprendimento, non ha molta importanza, ma se ci sono dei caveat o "trucchi" sui tratti del C++ 11, mi piacerebbe capire cosa sono. –

+0

Non penso sia un problema con i tratti di tipo C++, piuttosto un problema con 'boost :: function'. 'std :: function' ha un' operator = 'che accetta un' std :: nullptr_t' tuttavia 'boost :: function' non ha questa capacità (se si nota l'operatore di assegnazione nel messaggio di errore la sua funzione' boost :: : : operator = (Functor) '). Quindi, i tratti di tipo C++ 11 sono corretti nel restituire 'true' perché ** è ** assegnabile, tuttavia' boost :: function' manca della funzionalità per gestire 'nullptr'. Inoltre, 'std :: is_assignable :: value' è corretto nel restituire false poiché' decltype (NULL) 'è di tipo' int'. –

risposta

8

[Mi dispiace di rispondere alla mia domanda, ma dal momento che ho imparato così tanto a riguardo, ho pensato che sarebbe stato meglio consolidare le informazioni qui. Jesse è stato una parte ENORME di aiutarmi a capire tutto questo, quindi per favore svia i suoi commenti sopra.]

Così, perché is_assignable restituire i seguenti risultati:

typedef boost::function<void()> F; 
std::is_assignable<F, std::nullptr_t>::value // true 
std::is_assignable<F, decltype(NULL)>::value // false 

nonostante il fatto che queste affermazioni sembrano contraddire questi risultati:

boost::function<void()> f; 
f = nullptr; // fails to compile 
f = NULL; // compiles correctly 

La prima cosa da notare è che qualsiasi i tratti del tipo basati sulle operazioni della libreria standard (is_constructible, is_assignable, is_convertible, ecc.) controllano solo una funzione con un'interfaccia valida che corrisponda ai tipi dati al modello. In particolare, non controllano se l'implementazione di tale funzione è valida quando tali tipi sono sostituiti nel corpo della funzione.

boost::function non dispone di un costruttore specifico per nullptr, ma ha un "catch-all" operatore di assegnamento modello (insieme ad un costruttore corrispondente):

template<typename Functor> 
BOOST_FUNCTION_FUNCTION& operator=(Functor const & f); 

Questa è la migliore corrispondenza per nullptr, perché non esiste un sovraccarico specifico per std::nullptr_t e questo non richiede alcuna conversione ad un altro tipo (oltre alla conversione in un const &). Poiché la sostituzione del modello ha trovato questo operatore di assegnazione, std::is_assignable<boost::function<void()>, std::nullptr_t> restituisce true.

Tuttavia, all'interno del corpo di questa funzione, Functor è un tipo richiamabile; ovvero, f(); dovrebbe essere una dichiarazione valida. nullptr non è un oggetto invocabile, di conseguenza, il codice seguente genera l'errore del compilatore che è stato elencati nella domanda:

boost::function<void()> f; 
f = nullptr; // fails to compile 

Ma perché std::is_assignable<boost::function<void()>, decltype(NULL)> ritorno false? boost::function non ha un operatore di assegnazione specifico per un parametro int, quindi perché non è lo stesso operatore di assegnazione del modello "catch-all" utilizzato per int e std::nullptr_t?

In precedenza ho semplificato il codice per questo operatore di assegnazione tralasciando gli aspetti metaprogrammazione, ma dal momento che sono ormai rilevanti, li aggiungerò indietro:

template<typename Functor> 
typename enable_if_c< 
      (boost::type_traits::ice_not< 
      (is_integral<Functor>::value)>::value), 
      BOOST_FUNCTION_FUNCTION&>::type 
operator=(Functor const & f) 

Dovrebbe essere abbastanza evidente che il Il costrutto metaprogrammazione enable_if_c viene utilizzato qui per impedire l'istanziazione di questo operatore di assegnazione quando il tipo del parametro è int (ovvero, quando is_integral restituisce true). Pertanto, quando il lato destro di un'istruzione di assegnazione è di tipo int, non vi sono operatori di assegnazione corrispondenti per boost::function. Questo è il motivo per cui std::is_assignable<boost::function<void()>, decltype(NULL)> restituisce false, poiché NULL è di tipo int (per GCC almeno).

Ma questo non spiega ancora perché f = NULL; compila correttamente. Per spiegare questo, è importante notare che il valore 0 è implicitamente convertibile in qualsiasi tipo di puntatore. boost::function sfrutta questo utilizzando un operatore di assegnazione che accetta un puntatore a una struttura privata.(Quanto segue è una versione molto semplificata del codice da boost::function, ma è sufficiente per dimostrare il mio punto):

namespace boost 
{ 
    template<typename R()> 
    function 
    { 
    private: 
     struct clear_type {} 
     //... 

    public: 
     BOOST_FUNCTION_FUNCTION& operator=(clear_type*); 
     //... 
    }; 
} 

Da clear_type è una struttura privata, qualsiasi codice esterno è in grado di creare un'istanza di esso. L'unico valore che può essere accettato da questo operatore di assegnazione è un puntatore nullo che è stato convertito implicitamente da 0. Questo è l'operatore di assegnazione chiamato con l'espressione f = NULL;.


Così che spiega perché il is_assignable e le dichiarazioni incarico di lavoro il modo in cui lo fanno, ma ancora non aiuta a risolvere il mio problema originale: come faccio a rilevare se un determinato tipo può accettare nullptr o NULL ?

Sfortunatamente, sono ancora limitato dai tratti del tipo a causa della loro capacità di rilevare solo se esiste un'interfaccia valida. Per nullptr, sembra che non ci sia una buona risposta. Con boost::function, esiste un'interfaccia valida per nullptr, ma l'implementazione del corpo della funzione non è valida per questo tipo, che causerà sempre un errore del compilatore per istruzioni come f = nullptr;.

Ma è possibile rilevare correttamente che NULL può essere assegnato a un determinato tipo, ad esempio boost::function, in fase di compilazione? std::is_assignable richiede che fornisca il tipo del secondo argomento. Sappiamo già che lo standard decltype(NULL) non funzionerà, dal momento che questo valore corrisponde a int. Potrei usare boost::function<void()>::function::clear_type* come tipo, ma questo è molto verboso e richiede che conosca i dettagli interni del tipo con cui sto lavorando.

Una soluzione elegante prevede la creazione di un tratto di tipo personalizzato, che proviene da Luc Danton in another post here on SO. Non descriverò i dettagli di questo approccio, in quanto sono spiegati molto meglio in altra domanda, ma il codice per il mio tipo personalizzato caratteristica può essere visto qui:

template<typename> struct Void { typedef void type; }; 

template<typename T, typename Sfinae = void> 
struct is_assignable_with_NULL: std::false_type {}; 

template<typename T> 
struct is_assignable_with_NULL<T, 
    typename Void< decltype(std::declval<T>() = NULL) >::type 
>: std::true_type {}; 

posso usare questo nuovo tipo di caratteristica simile a std::is_assignable, ma ho solo bisogno di fornire il tipo di oggetto sul lato sinistro:

is_assignable_by_NULL<boost::function<void()>::value 

Come tutti i caratteri morfologici, questo sarà ancora solo controllare per un'interfaccia valida, ignorando la validità del corpo della funzione , ma alla fine mi consente di determinare correttamente se NULL può essere assegnato a boost::function (e qualsiasi altro tipo) in fase di compilazione.

+1

Non c'è niente di sbagliato nel rispondere alla tua domanda quindi per favore non sentirti male. Non si sa mai, i tuoi sforzi potrebbero aiutare qualcun altro –

Problemi correlati