12

当使用 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 */and/* 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_;
};

如果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_;
};

由于nullptr目前无法分配给boost::function,我希望选择第二个实现。但是,由于is_assignableis 返回trueforboost::functionnullptr,因此选择了第一个实现,这会导致deactivate函数中出现编译错误。

4

1 回答 1

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,但它确实有一个“包罗万象”的模板赋值运算符(以及相应的构造函数):

template<typename Functor>
BOOST_FUNCTION_FUNCTION& operator=(Functor const & f);

这是 的最佳匹配nullptr,因为没有特定的重载 forstd::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

但是为什么会std::is_assignable<boost::function<void()>, decltype(NULL)>返回falseboost::function参数没有特定的赋值运算符int,那么为什么不使用相同的“包罗万象”模板赋值运算符intand 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)

不言而喻,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*);
        //...
    };
}

由于clear_type是私有结构,因此任何外部代码都无法创建它的实例。此赋值运算符可以接受的唯一值是一个从 隐式转换的空指针0。这是使用表达式调用的赋值运算符f = NULL;


这就解释了为什么 theis_assignable和 assignment 语句以它们的方式工作,但它仍然不能帮助我解决我原来的问题:我如何检测给定类型是否可以接受nullptrNULL

不幸的是,我仍然受限于类型特征,因为它们只能检测是否存在有效接口。对于nullptr,似乎没有好的答案。有了boost::function,对于 来说确实存在一个有效的接口nullptr,但是函数体的实现对于这种类型是无效的,对于诸如f = nullptr;.

但是我可以在编译时 正确检测到NULL可以分配给给定类型,例如吗?要求我提供第二个参数的类型。我们已经知道这是行不通的,因为它的计算结果为. 我可以用作类型,但这非常罗嗦,需要我知道我正在使用的类型的内部细节。boost::functionstd::is_assignabledecltype(NULL)intboost::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 {};

我可以使用类似于 的这种新类型特征std::is_assignable,但我只需要在左侧提供对象的类型:

is_assignable_by_NULL<boost::function<void()>::value

像所有类型特征一样,这仍然只会检查有效接口,忽略函数体的有效性,但它最终允许我boost::function在编译时正确确定是否可以将 NULL 分配给(和任何其他类型)。

于 2012-04-23T20:41:27.190 回答