3

I have a problem when binding a function object created by bind as an argument to a function.

I have two functions fa and fb. fa takes a function as a parameter. I bind fa and the function (fb in that case) into a closure. I do it in two different ways, given below. The first one works, the second one does not compile.

(I know, I would not need to bind on the marked line since there are no parameters. But of course this is only the minimal example to show the behavior, in practice I will bind parameters to fb)

void fa(function<void()> f){
    f();
}

void fb(){

}

void test_which_compiles() {
    auto bb = fb; // *** QUESTIONABLE LINE ***
    auto aa = std::bind(fa, bb);
    aa();
}

void test_which_fails() {
    auto bb = std::bind(fb); // *** QUESTIONABLE LINE ***
    auto aa = std::bind(fa, bb);
    aa(); // COMPILE ERROR HERE
}

The compile error happens when invoking aa().

So it seems that the compiler does not accept when I bind a function object as an argument which was itself created by bind.

The compile error is a long list of stuff that I would not consider directly related to the line. I only give the first line:

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/type_traits:3476:13: error: no matching function for call to '__invoke' __invoke(_VSTD::declval<_Fp>(), _VSTD::declval<_Args>()...)

The compiler is an Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn) with C++11 enabled.

Michael
  • 7,407
  • 8
  • 41
  • 84

2 Answers2

5

std::bind returns some unspecified functor which has magical behaviour when nested in another bind expression. You can disable this magical behaviour by wrapping that return type, eg. by declaring bb as:

std::function<void()> bb = std::bind(fb);

and this works identically if bb really has parameters:

std::function<void()> bb = std::bind(fb, 42);

For an explanation, see the second bullet under Member function operator() here:

If std::is_bind_expression<T>::value == true (i.e. another bind subexpression was used as an argument in the initial call to bind), then that bind subexpression is invoked immediately and its result is passed to the invocable object. If the bind subexpression has any placeholder arguments, they are picked from u1, u2, ....

This example shows both the nested bind-expression behaviour and (I think) the behaviour you actually wanted. Edit - now also including Yakk's forwarding wrapper from the comment: more typing than using function or a lambda, but much more lightweight.

#include <iostream>
#include <functional>

void f(int n1, int n2, int n3) {
    std::cout << n1 << ' ' << n2 << ' ' << n3 << '\n'; }
void g(int n1, std::function<int()> fun, int n3) {
    std::cout << n1 << ' ' << fun() << ' ' << n3 << '\n'; }
int h(int n) { return n; }

namespace Yakk {
    template <class F> struct f_t {
        F f;
        template <class...Ts>
        std::result_of_t<F const&(Ts...)> operator()(Ts&&...ts) const& {
            return f(std::forward<Ts>(ts)...);
        } /* similar for &, &&, const&& */
    };  

    template <class F> f_t<std::decay_t<F>> f(F&& fin) {
        return {std::forward<F>(fin)};
    }
}

int main() {
    using namespace std::placeholders;
    auto subexpr = std::bind(h, _2);
    auto magic = std::bind(f, _1, subexpr, 42);
    magic(7, 11);

    std::function<int()> nested = std::bind(h, 11);
    auto nomagic = std::bind(g, _1, nested, 42);
    nomagic(7);

    auto yakkmagic = std::bind(g, _1, Yakk::f(std::bind(h, 11)), 42);
    yakkmagic(7);
}
Useless
  • 64,155
  • 6
  • 88
  • 132
  • But the unspecified function _is_ convertible to `std::function`, so it is reasonable to expect it to work, as any other functor type convertible to `std::function` would. It doesn't work because of a specific feature of `std::bind`, not just because the type is unspecified. – Jonathan Wakely Sep 16 '15 at 14:21
  • Yes, I just realized that when I examined bind more carefully - I hadn't noticed the subexpression stuff before – Useless Sep 16 '15 at 14:22
  • `template struct f_t{F f; template std::result_of_t operator()(Ts&&...ts)const&{return f(std::forward(ts)...);} /* similar for &, &&, const&& */ }; template f_t> f(F&& fin){return {std::forward(fin)};}` is a minimal, cost-free wrapper that hides the `std::bind`-ness of `std::bind` results. – Yakk - Adam Nevraumont Sep 16 '15 at 19:51
3

The problem is that std::bind is specified to invoke nested bind expressions, so when you call aa() it doesn't pass bb to fb. Instead it passes the argument list (which is empty) to bb, so it calls bb(), and then passes the result of that to fa , which expects a std::function<void()> so you get an error.

Boost.Bind provides the protect utility to wrap a nested bind expression to prevent it being recursively evaluated, but there is no std::protect. You can achieve a similar effect by wrapping it in a std::function:

void test_which_fails() {
    auto bb = std::bind(fb);
    auto aa = std::bind(fa, std::function<void()>(bb));
    aa();
}

Or in a lambda:

void test_which_fails() {
    auto bb = std::bind(fb);
    auto aa = std::bind(fa, [=]{ bb(); });
    aa();
}

but if you're going to use lambdas you might as well not bother with std::bind at all.

Community
  • 1
  • 1
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Is there a reason for this behavior? In my eyes, it's an extremely strange idea. Why should `std::bind` return different values depending from where it is called? That's completely in contrast to the behavior of functions in any programming language I know. Furthermore, one does definitely not expect it, it's more or less a hidden trap. – Michael Sep 16 '15 at 17:41
  • It doesn't return different values depending where it's called. Calling `bb` always returns `void`, and calling `aa` always returns `void` too. The reason it works like that is given at the link to the Boost docs in my answer, so you can compose multiple functions together and have one provide inputs to the next. – Jonathan Wakely Sep 16 '15 at 20:53