首页 文章

如何绑定传递未指定的调用包装器的成员函数模板

提问于
浏览
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 2012年11月CTP)

和g:

编译完成时出现错误:source.cpp:在实例化'X :: X(T)[with T = std :: _ Bind(Y *)>]'时:source.cpp:28:33:从这里获取源代码 . cpp:8:9:错误:无法调用'(std :: _ Bind_helper(Y *)>),X * const,std :: _ Bind(Y *)>&> :: type {aka std :: _ Bind (Y *)>)>(X *,std :: _ Bind(Y *)>)>})()'

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

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

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

2 回答

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

    UPDATE:

    上述解决方案是有助于实现您在示例中显示的内容的解决方法 . 但是,从评论中可以看出,您的用例可能大不相同(例如,传递 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();
        }
    };
    
  • 4

    我认为他们在 std::bind 中采用 boost::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实现 . 要保护绑定函数对象不受评估,请使用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(); });
    

相关问题