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

时间:2012-06-29 01:55:48

标签: c++ boost boost-bind

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

来自boost :: bind docs:

  

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

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

在我的情况下,发生了隐式转换,将绑定表达式中使用的类型转换为函数接收的类型。我期待这种转换发生在绑定的站点,但是当使用结果函数对象时会发生这种转换。

回想起来,我应该能够从类型不兼容only at the call site, not the bind site时使用boost :: bind的错误中解决这个问题。

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

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

    但考虑到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: %d\n", funcObj());
    }
    

4 个答案:

答案 0 :(得分:4)

因为仿函数可能支持多个重载,这可能会产生不同的行为。即使这个签名可以在你知道所有参数时解决(我不知道标准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转换为浮点数仍然是不可判定的。

答案 1 :(得分:2)

我认为这是因为bind必须与任何可调用的实体一起使用,无论是函数指针std::function<>,还是你自己的struct operator()仿函数。这使得可以使用()调用的任何类型的绑定通用。即绑定对您的仿函数的隐含要求只是它可以与()

一起使用

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

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

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

答案 2 :(得分:2)

在不同的情况下,您需要要在呼叫站点处理的参数。

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

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

答案 3 :(得分:0)

一个很好的例子是将“std :: future”绑定到普通类型的普通函数:

说我想以令人难以置信的异步方式使用普通的f(x,y)函数。也就是说,我想把它称为“f(X.get(),Y.get())”。有一个很好的理由 - 我可以调用该行,只要两个输入都可用,f就会运行f逻辑(我不需要单独的连接代码行)。要做到这一点,我需要以下内容:

1)我需要支持隐式转换“std :: future&lt; T&gt; - &gt; 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&lt; T&gt; - &gt; T”转换,我只等待所有输入参数在我执行CALL“stub()”时变为可用。如果它在绑定时通过运算符T()进行转换,那么当我实际构造“存根”时,逻辑会默默地强制等待,而不是在我使用它时。如果“stub()”不能总是在我构建它的同一个线程中安全地运行,那么这可能是致命的。

还有其他用例也迫使设计选择。这个精心设计的异步处理只是我个人所熟悉的。