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