7

我尝试使用 VC11 和 g++ 4.7.2 编译以下示例:

#include <functional>

class X {
public:
  template <typename T>
  explicit X(T t)
  {
    std::bind(&X::invoke<T>, this, t)();
  }
private:
  template <typename T>
  void invoke(T t)
  {
    t();
  }
};

class Y {
  public:
    void foo() {
      //...
    }
};


int main() {
  Y y;
  X x(std::bind(&Y::foo, &y));
  return 0;
}

但它以错误结束。我不确定粘贴整个编译器输出是否合理,但通常

vc11 说:

错误 C2664: 'void std::_Pmf_wrap::operator ()(_Farg0 &,_V0_t) const' : 无法将参数 3 从 'void' 转换为 'std::_Bind,Y *,std::_Nil,std::_Nil ,std::_Nil,std::_Nil,std::_Nil,std::_Nil>' c:\program files (x86)\microsoft visual studio 11.0\vc\include\functional 1152 1 ConsoleApplication1 (Microsoft Visual C++ Compiler Nov 2012 CTP)

和 g++:

编译完成但出现错误:
source.cpp: In instance of 'X::X(T) [with T = std::_Bind(Y*)>]':
source.cpp:28:33: required from here
source.cpp :8:9: 错误:不匹配调用 '(std::_Bind_helper(Y*)>), X* const, std::_Bind(Y*)>&>::type {aka std::_Bind( Y*)>)>(X*, std::_Bind(Y*)>)>}) ()'

有没有办法解决这个问题。保存主要思想对我来说非常重要——一个可以用任何可调用对象(函数对象、函数指针或函数返回的调用包装器std::bind())实例化的类。

如果有人帮助,我将不胜感激。

PS如果我创建一个实例X,传递函数对象或函数指针,它就会编译。

4

2 回答 2

4

boost::bind我认为他们在采用它时省略了一个重要的部分std::bind,即boost::protect()。您的代码可以固定如下:

#include <boost/bind/protect.hpp>
// ...
X x(boost::protect(std::bind(&Y::foo, &y)));

或者,或者:

template <typename T>
explicit X(T t)
{
    auto tt = boost::protect(t);
    auto f = std::bind(&X::invoke<decltype(tt)>, this, tt);
    f();
}

http://www.boost.org/doc/libs/1_53_0/libs/bind/bind.html

尽管默认情况下第一个参数不被评估,但所有其他参数都是。有时不必计算第一个参数之后的参数,即使它们是嵌套的绑定子表达式。这可以借助另一个函数对象protect来实现,它掩盖了类型,使bind无法识别和评估它。调用时,protect 只是将参数列表转发给未修改的其他函数对象。

头文件 boost/bind/protect.hpp 包含保护的实现。要保护绑定函数对象免于评估,请使用protect(bind(f, ...))。


Scott Meyers即将出版的Effective C++11: Content and Status将推荐lambdas 优于 std::bind。在 C++11 中,您可以简单地执行以下操作:

template <typename T>
explicit X(T t)
{
    auto f = [t, this]() { this->invoke(t); };
    f();
}
// ...

X x([&y](){ y.foo(); });
于 2013-02-27T15:30:11.473 回答
2

问题的根本原因似乎是由 执行其参数的内部复制std::bind,特别是对t.

您可能希望以这种方式解决它:

  template <typename T>
  explicit X(T t)
  {
      std::bind(&X::invoke<T>, this, std::placeholders::_1)(t);
      //                             ^^^^^^^^^^^^^^^^^^^^^  ^
  }

这也可以,但是不允许您使参数的结果bind超过参数t(否则,您将传递对 的悬空引用invoke<T>()):

  template <typename T>
  explicit X(T t)
  {
      std::bind(&X::invoke<T>, this, cref(t))();
      //                             ^^^^^^^
  }

更新:

上述解决方案是有助于实现您在示例中显示的内容的解决方法。但是,从评论中可以看出,您的用例可能完全不同(例如,传递结果以bind供以后评估)。

正如Maxim Yegorushkin在他的回答中正确指出的那样,概念上正确的解决方案包括使用诸如 Boost's 之类的构造protect

如果您不想使用 Boost,protect()使用 C++11 的可变参数模板定义自己的函数非常容易:

// Imitates boost::protect, but with variadic templates and perfect forwarding
namespace detail
{
    template<typename F>
    struct protect
    {
    private:

        F _f;

    public:

        explicit protect(F f): _f(f)
        {
        }

        template<typename... Ts>
        auto operator () (Ts&&... args) -> 
            decltype(_f(std::forward<Ts>(args)...))
        {
            return _f(std::forward<Ts>(args)...);
        }
    };
}

template<typename F>
detail::protect<F> protect(F&& f)
{
    return detail::protect<F>(std::forward<F>(f));
}

最终,这就是你可以在课堂上使用它的方式,正如 Maxim 建议的那样:

class X
{
public:
    template <typename T>
    explicit X(T t)
    {
        auto pt = protect(t);
        std::bind(&X::invoke<decltype(pt)>, this, pt)();
    }
private:
    template <typename T>
    void invoke(T t)
    {
        t();
    }
};
于 2013-02-27T15:14:13.193 回答