如果函数声明匹配,是否有不需要占位符的 std::bind 替代方法?

问题描述

我的 C++17 代码中经常出现以下场景。

通用示例:

class Receiver{
public:
    typedef std::function<void(const std::vector<uint8_t>& payload)> PROCESS_PACKET;
    PROCESS_PACKET m_callback;
    void run(){
        while(){
            // do stuff,then call callback:
            m_callback(data);
        }
    }
};

class UsesReceiver{
public:
    UsesReceiver(){
        receiver.m_callback=std::bind(&UsesReceiver::processData,this,std::placeholders::_1);
        receiver.run();
    }
private:
    void processData(const std::vector<uint8_t>& payload){
        // here do something implementation specific
    }
    Receiver receiver;
};

一直困扰我的是这部分:

receiver.m_callback=std::bind(&UsesReceiver::processData,std::placeholders::_1);

对于这个例子,它非常简单,因为函数只接受一个参数,但是一旦你有更多的参数,这部分在我看来就是很多样板。

因为我在想: 如果有一个成员函数的定义与声明的函数指针完全相同(例如参数和函数类型匹配),则应该有一个简单的单行程序,在编译时检查这些前提条件,基本上如下所示:

receiver.m_callback=std::bind_quick(&UsesReceiver::processData,this);

其中 std::bind_quick 是“就像 std::bind”,但如果两个函数声明都匹配(在编译时检查),则没有占位符。

由于前几条评论是“改用 lambdas”: 这并不能真正解决问题,因为对于 lambda,您仍然必须为函数参数声明“占位符”:

receiver.m_callback=[this](auto && PH1) { 
            processData(std::forward<decltype(PH1)>(PH1)); 
        };

解决方法

正如评论中所指出的,您通常应该更喜欢 lambdas 而不是 std::bind,即使它不能解决您的问题。

不过,使用 lambda 表达式创建 quick_bind 函数相当容易。

template <typename Func,typename Obj>
auto quick_bind(Func f,Obj* obj) {
    return [=](auto&&... args) {
        return (obj->*f)(std::forward<decltype(args)>(args)...);
    };
}

然后像这样使用它

receiver.m_callback=quick_bind(&UsesReceiver::processData,this);

它返回一个带有模板运算符的 lambda,因此如果您将它分配给 std::function,只要函数签名中没有不匹配,它就会起作用。

,

我认为您应该停止在 中使用 std::bind

Lambdas 解决了 99.9% 的 std::bind 问题,并且以更易于理解的方式解决了问题,您甚至不应该编写剩余的 0.1% std::bind 代码(这涉及传递 {{1 }} 到 std::bind 以及由此产生的疯狂)。

在原始数据中我们得到:

std::bind

在我看来,这已经更简单明了。只是语言原语,不涉及库函数。

如果你想要一个辅助函数

中,使用 receiver.m_callback=[this](auto&payload){processData(payload);}; 或在 中:

std::bind_front

有点乱,但给你

template<class F,class...Args>
auto bind_front( F&& f,Args&&...args ) {
  return [f = std::forward<F>(f),tup=std::make_tuple(std::forward<Args>(args)...)](auto&&... more_args)
         ->decltype(auto)
  {
    return std::apply([&](auto&&...args)->decltype(auto){
      return std::invoke( f,decltype(args)(args)...,decltype(more_args)(more_args)... );
    },tup);
  };
}

Live example

,

因为我在其中一个评论中也得到了一个非常好的解决方案,所以我将在这里总结一下:

如果您有幸使用 c++20,可以按照@StoryTeller 的建议使用 std::bind_front。 对于上面的示例,它看起来像这样:

receiver.m_callback=std::bind_front(&UsesReceiver::processData,this);

如果您没有 c++20 支持,您也可以使用 absl::bind_front。 摘录:

absl::bind_front() 的简单语法可以让您避免已知 滥用 std::bind()

此外,您也可以按照上面的答案或@NathanOliver 的评论中的建议编写自己的宏或函数。