8

I am trying to understand the concept of currying and calling a function which concats three strings but by passing only two strings and using the second argument twice.

However when I do this, the second argument is not getting sent to the function at all and it prints out an empty string. Is it some really obvious mistake?

string concatthreestrings(string a,string b,string c){
    cout<<"Value of A: "<<a<<endl;
    cout<<"Value of B: "<<b<<endl;
    cout<<"Value of C: "<<c<<endl;
    return a+b+c;
}


int main()
{
    typedef std::function< string( string,string) > fun_t ;
    using namespace std::placeholders;
    fun_t fn = std::bind( concatthreestrings, _1, _2, _2);
    cout<<endl<<fn( "First","Second")<<endl;

}

This is giving the below output. Doesnt using the _2 twice mean that second argument be passed for both second and third. If a use a string in its place its working fine.

enter image description here

ganeshran
  • 3,512
  • 7
  • 41
  • 69
  • Very interesting question, I would not have expected such a behavior! – Luc Touraille Mar 30 '12 at 14:53
  • 1
    Just to add some correction: This is not really currying. Binding arguments and currying are two very similar but still distinct operations, which should not be confused. Currying means to take a function which takes a function of N arguments and turn it into a function of one argument which returns a function of one argument which returns a function of one argument ... (repeated n Times). You can use `std::bind` to implement a `curry` function which does this for you (to some extend). Similarly you can use currying to implement argument binding in the way of `std::bind`. – LiKao Mar 30 '12 at 16:49
  • 1
    @LiKao: Indeed, `bind` allow [partial application](http://en.wikipedia.org/wiki/Partial_application), not currying. – Luc Touraille Mar 30 '12 at 19:49
  • Thanks LiKao and Luc, I get the difference now. I was under the impression that in this particular example bind is being used to curry one function call to another – ganeshran Mar 31 '12 at 06:55
  • @LiKao, Luc Touraille: This behavior doesnt seem to happen in VC++ compiler. Looks like its a GCC feature – ganeshran Apr 02 '12 at 06:31
  • I asked a follow-up to this question, which could shed some lights on your problem: [Is `std::function` allowed to move its arguments?](http://stackoverflow.com/q/10008503/20984) – Luc Touraille Apr 04 '12 at 12:03

2 Answers2

5

Copying strings is expensive. Since std::bind thinks that the values of the placeholders are only used once, it performs a std::move on the strings. This is done for each Parameter and as a consequence, either b or c is a moved, that means empty string.

You can change that behavior by explicitly saying what you mean, by passing the arguments by const-reference:

string concatthreestrings(string const& a,string const& b,string const& c)

Now, it should work.

ipc
  • 8,045
  • 29
  • 33
  • 2
    Are you sure this behavior is well-defined? From what I read in the standard, the wrapper returned by `bind` forwards its arguments to the wrapped function. In this case, according to my understanding, we would have a wrapper along this way (after deducing the template parameters and collapsing): `string g(const char (&u1)[6], const char (&u2)[7]) { concatthreestrings(forward(u1), forward(u2), forward(u2)); }`. The three strings `a`, `b` and `c` would be constructed from the arrays, so I don't see where the move would occur. – Luc Touraille Mar 30 '12 at 15:46
  • @ipc: Is there some way where i can specify this behavior in the std::bind rather than changing the function definition itself – ganeshran Mar 31 '12 at 06:56
  • Accessing moved object is UB. – BЈовић Apr 04 '12 at 11:00
2

I did a few tests using this smaller example that exhibits the same behavior you have:

#include <functional>
#include <iostream>
#include <string>

using std::string;

void print(string s1, string s2)
{
    std::cout << s1 << s2 << '\n';
}

int main()
{
    using namespace std::placeholders;

    typedef std::function< void(string) > fn_t;

    fn_t func = std::bind(print, _1, _1);

    std::string foo("foo");
    func(foo);
}

// outputs: foo

Note that I defined a string object named "foo" instead of using string literals. The behavior is the same, so the problem is not related to this.

I think the problem comes from your typedef. The return of bind (which is unspecified) is casted to a function taking a string by value, while the wrapper returned by bind probably take its arguments by rvalue-reference and perfectly-forward them. Instead of using your own typedef, you should use the auto keyword, so that the type of func will be automatically deduced by the compiler. If we modify the main as follows, we obtain the expected behavior:

int main()
{
    using namespace std::placeholders;

    auto func = std::bind(print, _1, _1);

    std::string foo("foo");
    func(foo);
}

// outputs: foofoo

Another solution is to replace your typedef so that func takes its parameter by reference-to-const:

typedef std::function< void(string const &) > fn_t;

I don't really understand why the other typedef does not work... Presumably the string is moved, as @ipc noted, but I don't know at what point of the execution this happens. I'm not even sure this is standard behavior, since both function and the wrapper returned by bind should use perfect forwarding. Perhaps GCC includes some optimizations that move the wrapper arguments when they are passed by value?

Edit

I did some tests, it turns out GCC's implementation of std::function performs a move on its arguments, while the wrapper return by std::bind does not. I still don't know if this is standard, I'm going to write a question about that.

Luc Touraille
  • 79,925
  • 15
  • 92
  • 137