使用gcc 4.7和boost 1.49时,使用以下表达式is_assignable返回true:
typedef boost::function<void()> F;
std::is_assignable<F, std::nullptr_t>::value
Run Code Online (Sandbox Code Playgroud)
但是,此代码无法编译:
boost::function<void()> f;
f = nullptr;
Run Code Online (Sandbox Code Playgroud)
产生以下错误消息:
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
Run Code Online (Sandbox Code Playgroud)
此外,此表达式返回false:
typedef boost::function<void()> G;
std::is_assignable<G, decltype(NULL)>::value
Run Code Online (Sandbox Code Playgroud)
但是这段代码确实编译:
boost::function<void()> g;
g = NULL;
Run Code Online (Sandbox Code Playgroud)
结果is_assignable似乎没有恰当地反映出功能boost::function.我在这里做错了吗?(我无法理解错误消息.)
我认为类型特征应该是确定模板中使用的类的功能的可靠方法.C++ 11中提供的类型特征是否与boost :: function不兼容?
为了给出这个背景,我一直在研究几个个人项目,以便更好地熟悉C++ 11的新功能.对于这个特定的项目,我正在尝试创建一个存储可以被"停用"的可调用函数的类.这大致是我正在尝试做的事情:
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_;
};
Run Code Online (Sandbox Code Playgroud)
对于/* func_ is active */和/* set func_ to deactive */块,我想提供两个不同的实现,这些实现在编译时根据属性选择F.如果nullptr可以分配给布尔上下文func_并且func_可以在布尔上下文中使用,那么我想使用以下内容(这是为内置函数指针选择的内容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_;
};
Run Code Online (Sandbox Code Playgroud)
如果nullptr无法分配func_,那么我想在存储"活动"状态的类中存储一个额外的布尔值.这个实现是为仿函数和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_;
};
Run Code Online (Sandbox Code Playgroud)
由于nullptr目前无法分配boost::function,我希望选择第二个实现.然而,由于is_assignable正在恢复true为boost::function和nullptr,第一个实现选择,而不是,这导致了一个编译错误deactivate功能.
[我对回答我自己的问题感到很难过,但由于我已经了解了很多,我认为最好在这里整合这些信息.杰西是帮助我理解所有这一切的巨大部分,所以请在上面提出他的评论.]
那么,为什么会is_assignable返回以下结果:
typedef boost::function<void()> F;
std::is_assignable<F, std::nullptr_t>::value // true
std::is_assignable<F, decltype(NULL)>::value // false
Run Code Online (Sandbox Code Playgroud)
尽管这些陈述似乎与这些结果相矛盾:
boost::function<void()> f;
f = nullptr; // fails to compile
f = NULL; // compiles correctly
Run Code Online (Sandbox Code Playgroud)
首先要注意的事情是,任何标准库的操作型为主的特征(的is_constructible,is_assignable,is_convertible等),只检查与匹配给模板类型有效的接口功能.特别是,当这些类型被替换为函数体时,它们不检查该函数的实现是否有效.
boost::function没有特定的构造函数nullptr,但它确实有一个"catch-all"模板赋值运算符(以及相应的构造函数):
template<typename Functor>
BOOST_FUNCTION_FUNCTION& operator=(Functor const & f);
Run Code Online (Sandbox Code Playgroud)
这是最佳匹配nullptr,因为没有特定的重载,std::nullptr_t并且这个不需要任何转换到另一种类型(除了转换为a const &).因为模板替换找到了这个赋值运算符,所以std::is_assignable<boost::function<void()>, std::nullptr_t>返回true.
但是,在这个函数体内,Functor预计是一个可调用类型; 也就是说,f();预计是一个有效的声明. nullptr因此,以下代码不会是可调用对象,会导致问题中列出的编译器错误:
boost::function<void()> f;
f = nullptr; // fails to compile
Run Code Online (Sandbox Code Playgroud)
但为什么要std::is_assignable<boost::function<void()>, decltype(NULL)>回归false? boost::function没有一个具体的赋值运算符int的参数,那么为什么不使用相同的"一揽子"模板赋值运算符int和std::nullptr_t?
之前我通过省略元编程方面简化了这个赋值运算符的代码,但由于它们现在相关,我将它们添加回来:
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)
Run Code Online (Sandbox Code Playgroud)
enable_if_c在这里使用元编程构造来防止在参数类型int(即is_integral返回时true)时实例化该赋值运算符应该是不言而喻的.因此,当赋值语句的右侧是类型时int,没有匹配的赋值运算符boost::function.这就是为什么std::is_assignable<boost::function<void()>, decltype(NULL)>返回false,因为NULL是类型int(至少对于GCC).
但这仍然无法解释为什么f = NULL;编译正确.为了解释这一点,重要的是要注意该值0可以隐式转换为任何指针类型. boost::function通过使用接受指向私有结构的指针的赋值运算符来利用它.(以下是代码的大大简化版本boost::function,但它足以证明我的观点):
namespace boost
{
template<typename R()>
function
{
private:
struct clear_type {}
//...
public:
BOOST_FUNCTION_FUNCTION& operator=(clear_type*);
//...
};
}
Run Code Online (Sandbox Code Playgroud)
由于clear_type是私有结构,因此任何外部代码都无法创建它的实例.此赋值运算符可以接受的唯一值是从中隐式转换的空指针0.这是使用表达式调用的赋值运算符f = NULL;.
这就解释了为什么is_assignable和赋值语句按照它们的方式工作,但它仍然无法解决我原来的问题:如何检测给定类型是否可以接受nullptr或NULL?
不幸的是,我仍然受限于类型特征,因为它们只能检测是否存在有效的接口.因为nullptr,似乎没有好的答案.使用时boost::function,确实存在一个有效的接口nullptr,但是对于这种类型,函数体的实现是无效的,这将导致诸如的语句的编译器错误f = nullptr;.
但我可以正确检测到NULL可以分配给给定类型,例如boost::function,在编译时吗? std::is_assignable要求我提供第二个参数的类型.我们已经知道这decltype(NULL)不会起作用,因为这是评估的int.我可以使用boost::function<void()>::function::clear_type*作为类型,但这非常罗嗦,并要求我知道我正在使用的类型的内部细节.
一个优雅的解决方案涉及创建一个自定义类型特征,来自Luc Danton在SO的另一篇文章中.我不会描述这种方法的细节,因为在另一个问题中它们的解释要好得多,但我的自定义类型特征的代码可以在这里看到:
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 {};
Run Code Online (Sandbox Code Playgroud)
我可以使用这种新类型的特性std::is_assignable,但我只需要在左侧提供对象的类型:
is_assignable_by_NULL<boost::function<void()>::value
Run Code Online (Sandbox Code Playgroud)
像所有类型特征一样,这仍将只检查有效的接口,忽略函数体的有效性,但它最终允许我正确地确定boost::function在编译时是否可以将NULL分配给(和任何其他类型).