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

Mic*_*rce 12 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
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正在恢复trueboost::functionnullptr,第一个实现选择,而不是,这导致了一个编译错误deactivate功能.

Mic*_*rce 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
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)>回归falseboost::function没有一个具体的赋值运算符int的参数,那么为什么不使用相同的"一揽子"模板赋值运算符intstd::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和赋值语句按照它们的方式工作,但它仍然无法解决我原来的问题:如何检测给定类型是否可以接受nullptrNULL

不幸的是,我仍然受限于类型特征,因为它们只能检测是否存在有效的接口.因为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分配给(和任何其他类型).