The following expression using is_assignable returns true when using gcc 4.7 and boost 1.49:
typedef boost::function<void()> F;
std::is_assignable<F, std::nullptr_t>::value
However, this code fails to compile:
boost::function<void()> f;
f = nullptr;
producing these error messages:
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
Additionally, this expression returns false:
typedef boost::function<void()> G;
std::is_assignable<G, decltype(NULL)>::value
but this code does compile:
boost::function<void()> g;
g = NULL;
The results of is_assignable don’t seem to properly reflect the functionality of boost::function. Am I doing something wrong here? (I’m having trouble making sense of the error messages.)
I thought the type traits were supposed to be a reliable way of determining the functionality of the classes used in templates. Are the type traits provided in C++11 simply incompatible with boost::function?
To give this some context, I’ve been working on several personal projects to better familiarize myself with the new features of C++11. For this particular project, I’m attempting to create a class that stores a callable function that can be “deactivated”. This is roughly what I’m trying to do:
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_;
};
For the /* func_ is active */ and /* set func_ to deactive */ blocks, I want to provide two different implementations that are selected at compile time depending on the properties of F. If nullptr can be assigned to func_ and func_ can be used in a boolean context, then I want to use the following (which is what gets selected for built-in function pointers and 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 cannot be assigned to func_, then I want to store an additional boolean value within the class that stores the “active” status. This implementation is selected for functors and lambda functions:
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_;
};
Since nullptr currently cannot be assigned to boost::function, I would expect the second implementation to be chosen. However, since is_assignable is returning true for boost::function and nullptr, the first implementation is selected instead, which results in a compilation error in the deactivate function.
[I feel bad about answering my own question, but since I’ve learned so much concerning it, I figured it would be best to consolidate that information here. Jesse was a HUGE part of helping me understand all of this, so please upvote his comments above.]
So, why does
is_assignablereturn the following results:despite the fact that these statements seem to contradict those results:
The first thing to note is that any of the operations-based type traits of the standard library (
is_constructible,is_assignable,is_convertible, etc.) only check for a function with a valid interface that matches the types given to the template. In particular, they do not check to see if the implementation of that function is valid when those types are substituted into the function body.boost::functiondoes not have a specific constructor fornullptr, but it does have a “catch-all” template assignment operator (along with a corresponding constructor):This is the best match for
nullptr, because there isn’t a specific overload forstd::nullptr_tand this one doesn’t require any conversions to another type (aside from the conversion to aconst &). Because the template substitution found this assignment operator,std::is_assignable<boost::function<void()>, std::nullptr_t>returnstrue.However, within the body of this function,
Functoris expected to be a callable type; that is,f();is expected to be a valid statement.nullptris not a callable object, therefore, the following code results in the compiler error that was listed in the question:But why does
std::is_assignable<boost::function<void()>, decltype(NULL)>returnfalse?boost::functiondoesn’t have a specific assignment operator for anintparameter, so why isn’t the same “catch-all” template assignment operator used forintandstd::nullptr_t?Earlier I simplified the code for this assignment operator by leaving out the metaprogramming aspects, but since they are now relevant, I’ll add them back:
It should be fairly self-evident that the metaprogramming construct
enable_if_cis used here to prevent the instantiation of this assignment operator when the type of the parameter isint(that is, whenis_integralreturnstrue). Thus, when the right hand side of an assignment statement is of typeint, there are no matching assignment operators forboost::function. This is whystd::is_assignable<boost::function<void()>, decltype(NULL)>returnsfalse, sinceNULLis of typeint(for GCC at least).But this still doesn’t explain why
f = NULL;compiles correctly. To explain this, it is important to note that the value0is implicitly convertible to any pointer type.boost::functionexploits this by using an assignment operator that accepts a pointer to a private structure. (The following is a greatly simplified version of the code fromboost::function, but it is sufficient for demonstrating my point):Since
clear_typeis a private structure, any external code is unable to create an instance of it. The only value that can be accepted by this assignment operator is a null pointer that was implicitly converted from0. This is the assignment operator that is called with the expressionf = NULL;.So that explains why the
is_assignableand the assignment statements work the way that they do, but it still doesn’t help me solve my original problem: how do I detect whether a given type can acceptnullptrorNULL?Unfortunately, I’m still limited with the type traits due to their ability to only detect whether a valid interface exists. For
nullptr, there appears to be no good answer. Withboost::function, a valid interface does exist fornullptr, but the implementation of the function body is invalid for this type, which will always cause a compiler error for statements such asf = nullptr;.But can I correctly detect that
NULLcan be assigned to a given type, such asboost::function, at compile time?std::is_assignablerequires that I provide the type of the second argument. We already know thatdecltype(NULL)won’t work, since this evaluates toint. I could useboost::function<void()>::function::clear_type*as the type, but this is very wordy and requires that I know the internal details of the type that I’m working with.An elegant solution involves creating a custom type trait, which comes from Luc Danton in another post here on SO. I won’t describe the details of this approach, as they are explained much better in the other question, but the code for my custom type trait can be seen here:
I can use this new type trait similarly to
std::is_assignable, but I only need to provide the type of the object on the left-hand side:Like all type traits, this will still only check for a valid interface, ignoring the validity of the function body, but it finally allows me to correctly determine whether NULL can be assigned to
boost::function(and any other type) at compile time.