Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Function bind repeating arguments to curried function

Tags:

c++

gcc

currying

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

like image 890
ganeshran Avatar asked Mar 30 '12 14:03

ganeshran


2 Answers

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.

like image 187
ipc Avatar answered Oct 22 '22 11:10

ipc


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.

like image 27
Luc Touraille Avatar answered Oct 22 '22 13:10

Luc Touraille