为什么 boost::bind 存储传入类型的参数,而不是函数期望的类型

Why does boost::bind store arguments of the type passed in rather than of the type expected by the function?

本文关键字:类型 函数 期望 参数 存储 为什么 boost bind      更新时间:2023-10-16

我最近在使用boost::bind时在代码中遇到了一个错误。

从提升::绑定文档:

绑定采用的参数由返回的函数对象在内部复制和保存。

我假设所持有的副本的类型是基于函数的签名。 但是,它实际上基于传入的值的类型。

在我的例子中,发生了隐式转换,将绑定表达式中使用的类型转换为函数接收的类型。 我期望这种转换发生在绑定的站点,但是当使用生成的函数对象时会发生。

回想起来,我应该能够从以下事实中弄清楚这一点:当类型仅在调用站点而不是绑定站点不兼容时,使用 boost::bind 会出错。

我的问题是:为什么 boost::bind 以这种方式工作?

  1. 它似乎给出了更糟糕的编译器错误消息
  2. 当发生隐式转换并且对函子有多个调用时,它似乎效率较低

但考虑到Boost的设计有多好,我猜是有原因的。 它的行为是从 std::bind1st/bind2nd 继承的吗? 是否有一个微妙的原因为什么这很难/不可能实现? 完全是别的什么?

为了测试第二种理论,我写了一个似乎有效的小代码片段,但很可能有一些绑定的特性我没有考虑到,因为它只是一个片段:

namespace b = boost;
template<class R, class B1, class A1>
   b::_bi::bind_t<R, R (*) (B1), typename b::_bi::list_av_1<B1>::type>
   mybind(R (*f) (B1), A1 a1)
{
   typedef R (*F) (B1);
   typedef typename b::_bi::list_av_1<B1>::type list_type;
   return b::_bi::bind_t<R, F, list_type> (f, list_type(B1(a1)));
}
struct Convertible
{
   Convertible(int a) : b(a) {}
   int b;
};
int foo(Convertible bar)
{
   return 2+bar.b;
}
void mainFunc()
{
   int x = 3;
   b::function<int()> funcObj = mybind(foo, x);
   printf("val: %dn", funcObj());
}

因为函子可能支持多个重载,这可能会产生不同的行为。即使您知道所有参数时可以解决此签名(我不知道标准C++是否可以保证此功能),bind也不知道所有参数,因此绝对无法提供。因此,bind没有必要的信息。

编辑:只是为了澄清,考虑

struct x {
    void operator()(int, std::vector<float>);
    void operator()(float, std::string);
};
int main() {
    auto b = std::bind(x(), 1); // convert or not?
}

即使您要反思结构并了解其重载,仍然无法确定是否需要将1转换为浮点数。

在不同情况下,您需要在调用站点处理参数。

第一个这样的例子是调用一个成员函数,您可以在对象的副本(boost::bind( &std::vector<int>::push_back, myvector))上调用成员,这很可能是您不想要的,或者您需要传递一个指针,绑定器将根据需要取消引用指针(boost::bind( &std::vector<int>::push_back, &myvector )) --请注意,这两个选项在不同的程序中都有意义

另一个重要的用例是通过引用函数来传递参数。 bind复制执行等效的按值传递调用。该库提供了通过辅助函数 refcref 包装参数的选项,这两个函数都存储指向要传递的实际对象的指针,并且在调用位置取消引用指针(通过隐式转换)。如果在绑定时执行到目标类型的转换,则无法实现。

我认为这是因为

绑定必须与任何可调用的实体一起使用,无论是函数指针、std::function<> ,还是您自己的函子struct operator()。这使得可以使用 () 调用的任何类型上的绑定泛型。即 Bind 对函子的隐含要求只是它可以与()一起使用

如果 bind 要存储函数参数类型,则必须以某种方式推断它们作为类型参数传入的任何可调用实体。这显然不会那么通用,因为如果不依赖用户指定某种typedef(例如),就不可能推断传入结构类型的operator()的参数类型。因此,对函子(或概念)的要求不再是具体/简单的。

我不完全确定这是原因,但这是一个问题。

编辑:正如DeadMG在另一个答案中提到的另一点,重载即使对于标准函数指针也会产生歧义,因为编译器将无法解析函子类型。通过存储您提供的绑定类型并使用 () ,也可以避免此问题。

一个很好的例子是将"std::future"绑定到一些采用普通类型的普通函数:

假设我想以一种令人难以置信的异步方式使用一个普通的 f(x,y) 函数。也就是说,我想像"f(X.get(),Y.get())"一样称呼它。这是有充分理由的 - 我可以调用该行,一旦两个输入都可用,f 的逻辑就会运行(我不需要单独的代码行进行连接)。为此,我需要以下内容:

1)我需要支持隐式转换"std::future-> T"。这意味着 std::future 或我的自定义等效项需要一个强制转换运算符:

operator T() { return get(); }

2)接下来,我需要绑定我的泛型函数以隐藏其所有参数

// Hide the parameters
template<typename OUTPUT, typename... INPUTS>
std::function<OUTPUT()> BindVariadic(std::function<OUTPUT(INPUTS...)> f,
                                     INPUTS&&... in)
{
   std::function<OUTPUT()> stub = std::bind( f, std::forward<INPUTS>(in)...);
   return stub;
}

使用 std::bind 在调用时执行"std::function -> T"转换,当我实际调用"STUB()"时,我只等待所有输入参数变得可用。如果它通过绑定处的运算符 T() 进行转换,则逻辑将在我实际构造"存根"而不是使用它时静默地强制等待。如果"stub()"不能总是在我构建它的同一线程中安全运行,那可能是致命的。

还有其他用例也迫使人们做出这种设计选择。这个用于异步处理的精心设计的产品正是我个人熟悉的。