使用std :: is_assignable,boost :: function和nullptr时出现意外结果

时间:2012-04-19 01:12:52

标签: c++ boost c++11

使用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_assignabletrue返回boost::function,因此会选择第一个实现,这会导致nullptr函数中的编译错误。

1 个答案:

答案 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_constructibleis_assignableis_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参数的特定赋值运算符,因此为什么intint使用的“全包”模板赋值运算符不同?

之前我通过省略元编程方面简化了这个赋值运算符的代码,但由于它们现在相关,我将把它们添加回来:

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,似乎没有好的答案。对于nullptrNULL存在一个有效的接口,但是对于这种类型,函数体的实现是无效的,这将导致nullptr等语句的编译器错误。 / p>

但我可以在编译时正确检测boost::function是否可以分配给给定类型,例如nullptrf = 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 {}; (以及任何其他类型) )在编译时。