与手动编织器相比,std :: mem_fn不足

时间:2013-02-08 09:29:44

标签: c++ c++11 implicit-conversion mem-fun

我遇到过一个用例,其中std::mem_fn无法执行手动包装函数可以执行的操作。当包装函数用于不属于方法类的东西,但是可以隐式转换为它的类型时,它会出现:

#include <functional>

struct A
{
};

struct B
{
    B(A);  // implicit conversion from A to B
    void foo() const;
};


auto foo1 = std::mem_fn(&B::foo);     // std::mem_fn

void foo2(const B& b) { b.foo(); }    // hand-rolled wrapper

int main()
{
    A a;
    foo1(a);  // doesn't work
    foo2(a);  // works fine
}

调用foo1的编译器错误如下(使用GCC 4.8):

In file included from test.cpp:1:0:
functional: In instantiation of '_Res std::_Mem_fn<_Res (_Class::*)(_ArgTypes ...)const>::_M_call(_Tp&, const volatile void*, _ArgTypes ...) const [with _Tp = A; _Res = void; _Class = B; _ArgTypes = {}]':
functional:608:42:   required from '_Res std::_Mem_fn<_Res (_Class::*)(_ArgTypes ...)const>::operator()(_Tp&, _ArgTypes ...) const [with _Tp = A; _Res = void; _Class = B; _ArgTypes = {}]'
test.cpp:21:11:   required from here
functional:586:13: error: no match for 'operator*' (operand type is 'A')
  { return ((*__ptr).*__pmf)(std::forward<_ArgTypes>(__args)...); }
             ^

是否有可能以这样的方式实现std::mem_fn,使得这个用例就像手工卷制的包装一样工作?

1 个答案:

答案 0 :(得分:5)

有可能,是的,但不是C ++标准如何指定mem_fn

标准规定foo1(a)调用INVOKE(&B::foo, a),其中[func.require]中将其定义为:

  

如下定义INVOKE (f, t1, t2, ..., tN)
   - (t1.*f)(t2, ..., tN)f是指向类T的成员函数的指针时,t1T类型的对象或对象的引用键入T或对从T派生的类型的对象的引用;
   - ((*t1).*f)(t2, ..., tN)当f是指向类T的成员函数的指针时,t1不是上一项中描述的类型之一;
   - ......

您的案例无法满足第一个项目符号的条件,因为a不是类B的对象,也不是对BB派生的类的引用1}},所以第二个项目符号适用,因此相当于((*a).*f)()无效。

它以这种方式定义,允许使用智能指针,例如

auto foo1 = std::mem_fn(B::foo);
auto p = std::make_shared<B>();
foo1(p);

INVOKE 的定义(bindfunctionasync以及创建调用的库的其他部分也使用了 t1 的定义包装器)意味着当调用包装指向成员的指针时,如果第一个参数T不是std::shared_ptr,则假定它是某种指针并被取消引用。这意味着它适用于std::unique_ptrstd::mem_fn,但也适用于boost::shared_ptr一无所知的类型,例如MyVeryOwnSmartPtrt1

为了使代码正常工作,可以添加额外的案例来处理T不是T或来自is_convertible<T>::value的类型,但是T(t1).*f)()是是的,并调用B,但这会使规范复杂化,并且在某些情况下可能会产生不良后果。

您的“包装器”将强制隐式转换其参数,但它无法处理mem_fn支持的A类型的智能指针或右值。如果您有特定情况要将B对象转换为mem_fn以调用该函数,那么只需执行此操作,通用INVOKE模板不适用,但它更多灵活和通用,适用于许多其他情况。

(注意 std::reference_wrapper 的定义实际上是有缺陷的,因为它取消引用a个对象的方式与取消引用{{1}}参数的方式相同。我是提议修复http://cplusplus.github.com/LWG/lwg-active.html#2219,但这不会影响您的示例。)