2012-04-19 8 views
12

La siguiente expresión usando is_assignable vuelve true cuando se utiliza gcc 4.7 y 1.49 impulsar:resultados inesperados cuando se usa std :: is_assignable, impulsar :: función y nullptr

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

Sin embargo, este código falla al compilar:

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

productoras de estos mensajes de error:

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 

Además, esta expresión devuelve false:

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

pero este código se compila: no parecen

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

Los resultados de is_assignable para reflejar adecuadamente la funcionalidad de boost::function. ¿Estoy haciendo algo mal aquí? (Tengo problemas para dar sentido a los mensajes de error.)

Pensé que los rasgos de tipo se suponía que eran una forma confiable de determinar la funcionalidad de las clases utilizadas en las plantillas. ¿Los rasgos de tipo proporcionados en C++ 11 son simplemente incompatibles con boost :: function?


para dar a este un poco de contexto, he estado trabajando en varios proyectos personales para familiarizarse mejor a mí mismo con las nuevas características de C++ 11. Para este proyecto en particular, intento crear una clase que almacene una función invocable que pueda ser "desactivada". Esto es más o menos lo que yo estoy tratando de hacer:

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

Para los /* func_ is active */ y /* set func_ to deactive */ bloques, quiero ofrecer dos implementaciones diferentes que se seleccionan en tiempo de compilación en función de las propiedades de F. Si nullptr se pueden asignar a func_ y func_ se puede utilizar en un contexto booleano, entonces yo quiero usar lo siguiente (que es lo que se ha seleccionado para los punteros de las funciones integradas y 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 no puede Asignar a func_, luego quiero almacenar un valor booleano adicional dentro de la clase que almacena el estado "activo". Esta aplicación es seleccionado para funtores y funciones 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_; 
}; 

Desde nullptr actualmente no se pueden asignar a boost::function, yo esperaría que la segunda aplicación que se ha elegido. Sin embargo, dado que is_assignable está devolviendo true para boost::function y nullptr, se selecciona la primera implementación en su lugar, lo que da como resultado un error de compilación en la función deactivate.

+5

Nota al margen: ¿Alguna razón para no usar 'std :: function' en lugar de' boost :: function'? –

+0

Me gustaría poder manejar los casos más comunes, que creí que incluirían punteros de funciones incorporados, funtores, funciones lambda, 'std :: function', y' boost :: function'. Mi versión actual funciona para todo, excepto para 'boost :: function', debido al problema mencionado anteriormente. Dado que este es solo un proyecto de aprendizaje, realmente no importa, pero si hay advertencias o "errores" en los rasgos de tipo C++ 11, me gustaría entender cuáles son. –

+0

No creo que sea un problema con los rasgos de tipo C++, sino un problema con 'boost :: function'. 'std :: function' tiene un' operator = 'que toma' std :: nullptr_t' pero 'boost :: function' carece de esta capacidad (si observa el operador de asignación en su mensaje de error es' boost :: function : : operator = (Functor) '). Entonces, los rasgos de tipo C++ 11 son correctos al devolver 'true' porque ** es ** asignable, sin embargo' boost :: function' carece de la funcionalidad para manejar 'nullptr'. Además, 'std :: is_assignable :: value' es correcto al devolver falso porque' decltype (NULL) 'es del tipo' int'. –

Respuesta

8

[Me siento mal por haber respondido mi propia pregunta, pero como he aprendido mucho al respecto, pensé que sería mejor consolidar esa información aquí. Jesse fue una GRAN parte de ayudarme a entender todo esto, así que por favor voten sus comentarios arriba.]

Así que, ¿por qué is_assignable devolver los siguientes resultados:

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

a pesar del hecho de que estas declaraciones parecen contradecir esos resultados:

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

La primera cosa a destacar es que cualquiera de los rasgos de tipo basados ​​en operaciones de la biblioteca estándar (is_constructible, is_assignable, is_convertible, etc.) solo buscan una función con una interfaz válida que coincida con los tipos dados a la plantilla. En particular, no verifican si la implementación de esa función es válida cuando esos tipos se sustituyen en el cuerpo de la función.

boost::function no tiene un constructor específico para nullptr, pero tiene un operador de asignación plantilla "catch-all" (junto con un constructor correspondiente):

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

Esta es la mejor combinación para nullptr, porque no hay una sobrecarga específica para std::nullptr_t y esta no requiere ninguna conversión a otro tipo (aparte de la conversión a const &). Como la sustitución de plantilla encontró este operador de asignación, std::is_assignable<boost::function<void()>, std::nullptr_t> devuelve true.

Sin embargo, dentro del cuerpo de esta función, se espera que Functor sea un tipo invocable; es decir, se espera que f(); sea una declaración válida. nullptr no es un objeto puede ser llamado, por lo tanto, el código siguiente produce el error de compilador que se enumeran en la pregunta: ¿

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

pero ¿por qué std::is_assignable<boost::function<void()>, decltype(NULL)> retorno false? boost::function no tiene un operador de asignación específico para un parámetro int, entonces ¿por qué no se utiliza el mismo operador de asignación de plantilla "general" para int y std::nullptr_t?

Anteriormente he simplificado el código para este operador de asignación al dejar fuera los aspectos metaprogramación, pero ya que ahora son relevantes, voy a añadir de nuevo:

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) 

Debe ser bastante evidente que la La construcción de metaprogramación enable_if_c se utiliza aquí para evitar la creación de instancias de este operador de asignación cuando el tipo del parámetro es int (es decir, cuando is_integral devuelve true). Por lo tanto, cuando el lado derecho de una declaración de asignación es del tipo int, no hay operadores de asignación coincidentes para boost::function. Esta es la razón por la que std::is_assignable<boost::function<void()>, decltype(NULL)> devuelve false, ya que NULL es del tipo int (para GCC al menos).

Pero esto todavía no explica por qué f = NULL; se compila correctamente. Para explicar esto, es importante tener en cuenta que el valor 0 es implícitamente convertible a cualquier tipo de puntero. boost::function explota esto utilizando un operador de asignación que acepta un puntero a una estructura privada.(La siguiente es una versión muy simplificada del código de boost::function, pero es suficiente para demostrar mi punto):

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

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

Desde clear_type es una estructura privada, cualquier código externo es incapaz de crear una instancia de la misma. El único valor que puede aceptar este operador de asignación es un puntero nulo convertido implícitamente de 0. Este es el operador de asignación al que se llama con la expresión f = NULL;.


Eso explica por qué el is_assignable y las instrucciones de asignación funcionan de la manera que lo hacen, pero todavía no ayuda a resolver mi problema original: ¿cómo puedo detectar si un tipo dado puede aceptar o nullptrNULL ?

Desafortunadamente, todavía estoy limitado con los rasgos de tipo debido a su capacidad para detectar solo si existe una interfaz válida. Para nullptr, parece que no hay una buena respuesta. Con boost::function, existe una interfaz válida para nullptr, pero la implementación del cuerpo de la función no es válida para este tipo, que siempre causará un error de compilación para sentencias como f = nullptr;.

Pero, ¿puedo detectar correctamente que NULL se pueden asignar a un tipo determinado, como boost::function, en tiempo de compilación? std::is_assignable requiere que proporcione el tipo del segundo argumento. Ya sabemos que decltype(NULL) no funcionará, ya que esto se evalúa como int. Podría usar boost::function<void()>::function::clear_type* como el tipo, pero esto es muy prolijo y requiere que conozca los detalles internos del tipo con el que estoy trabajando.

Una solución elegante implica la creación de un rasgo de tipo personalizado, que proviene de Luc Danton en another post here on SO. No voy a describir los detalles de este enfoque, ya que se explican mucho mejor en la otra pregunta, pero el código de mi tipo personalizado rasgo se puede ver aquí:

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

puedo utilizar este nuevo rasgo Tipo de manera similar a std::is_assignable, pero sólo tendrá que proporcionar el tipo de objeto en el lado izquierdo:

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

igual que todos los rasgos de tipo, esto sigue siendo sólo comprobar si hay una interfaz válida, haciendo caso omiso de la validez del cuerpo de la función , pero finalmente me permite determinar correctamente si NULL se puede asignar a boost::function (y cualquier otro tipo) en tiempo de compilación.

+1

No hay nada de malo en responder su propia pregunta, así que no se sienta mal. Nunca se sabe, sus esfuerzos podrían ayudar a alguien más –

Cuestiones relacionadas