【问题标题】:How do I bind a member function template passing unspecified call wrapper如何绑定传递未指定调用包装器的成员函数模板
【发布时间】:2013-02-13 11:01:35
【问题描述】:

我尝试使用 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++ 编译器 2012 年 11 月 CTP)

和 g++:

编译完成但出现错误:
source.cpp:在 'X::X(T) [with T = std::_Bind(Y*)>]'的实例化中:
source.cpp:28:33: 从这里需要
source.cpp:8:9: 错误: 调用不匹配 '(std::_Bind_helper(Y*)>), X* const, std::_Bind(Y*)>&>::type {aka std: :_Bind(Y*)>)>(X*, std::_Bind(Y*)>)>}) ()'

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

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

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

【问题讨论】:

    标签: c++ c++11 stdbind


    【解决方案1】:

    我认为他们在将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 将推荐优先使用 lambda 而非 std::bind。在 C++11 中,您可以简单地执行以下操作:

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

    【讨论】:

    • 即将推出的 Effective C++11:Content and Status 以“Effective Modern C++”的形式提供。第 34 条是“更喜欢 lambda 而不是 std::bind”,并得出结论“Lamda 更具可读性,更具表现力......”。
    • @JeanDavy 几个月前没发布吗?
    • 哎呀,是的。上个月我在使用 Java 和 Python,所以我忽略了 C++,现在我又回到了我的初恋;-),我刚买了它。
    【解决方案2】:

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

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

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

    这也可以,但不允许您使bind 的结果比t 的参数更有效(否则,您将传递对invoke&lt;T&gt;() 的悬空引用):

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

    更新:

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

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

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

    // 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();
        }
    };
    

    【讨论】:

    • 在我看来,它解决了一个有些不同的问题。原始绑定表达式在某种意义上是自包含的,它存储所有参数的副本,以便可以销毁参数。您的解决方案省略了仿函数的复制,并依赖于它在调用时仍在范围内并且处于活动状态。实际上,创建绑定表达式然后立即调用它是没有意义的。相反,您希望将 bind 创建的函子传递到其他地方,并且经常延迟调用。
    • @MaximYegorushkin:确实。尽管如此,对于 OP 的用例来说,这仍然是一种有效的解决方法。
    • 不确定这是否是他的真实用例。请参阅我更新的先前评论。
    • @MaximYegorushkin:我理解你的评论。根据 OP 提供的示例,这提供了一个 workaround (这是我使用的词)。我并不是说它是一个概念上完美的解决方案。尤其是我建议的第一个解决方法,OP 不会被强制立即调用 bind 的结果;它可以单独复制参数。同样,我并不是说这个解决方案是完美的,更不用说比你的更好了,我只是说它提供了一个解决方法。例如,OP 可能不想使用 boost。
    • 谢谢安迪,std::bind(&X::invoke, this, cref(t))() 解决了我的问题。如果我打算在 ctor 中启动一个线程,将 invoke() 方法绑定为线程入口点,它也会很有用。
    猜你喜欢
    • 2011-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-23
    • 1970-01-01
    • 1970-01-01
    • 2018-12-07
    相关资源
    最近更新 更多