Irad K
Irad K

Reputation: 877

Passing variable from type std::function fails although the caller expect the exact same type

for some reason I cannot pass value to method with the exact type.

But if I cast the variable I wish to pass using decltype of itself, it somehow works (?) Perhaps anybody know what's the reason.

Also, When I pass the lambda block to std::function it doesn't require any casting, although they're not exactly the same.

the following example demonstrate my findings :

#include <functional>

class A {
public:
    A(std::function<void(std::string&, int)> &&f): _f(f) { }

    std::function<void(std::string&,int)> _f;
};

class B : public A {
public:
    //B(std::function<void(std::string&, int)> &&f) : A(f) { } --> fail on casting error, why ?
    B(std::function<void(std::string&, int)> &&f) : A((decltype(f)) f) { }
};


class C {
public:
    C(std::function<void(std::string&,int)> f) {
        //b = new B(f); --> this one trigger casting error, why ? 
        b = new B((decltype(f)) f);
    }
    B *b;
};

int main(int argc, const char * argv[]) {
    // this works for some reason, even though C c'tor expect std::function ... why ? 
    C c([=](std::string&a, int b) {
        printf("%s %d\n", a.c_str(), b);
    });
}

Upvotes: 1

Views: 113

Answers (4)

frogatto
frogatto

Reputation: 29285

// this works for some reason, even though C c'tor expect std::function ... why ?

C c([=](std::string&a, int b) {
       printf("%s %d\n", a.c_str(), b);
    });

Since the constructors are not explicit, the compiler looks for a constructor that the passed value can be implicitly converted to. So, the passed value, i.e. the lambda, can be converted to a std::function through one of std::function constructors.

//b = new B(f); --> this one trigger casting error, why ? 
b = new B((decltype(f)) f);

B's constructor expects a rvalue but f is a lvalue. That causes the error. However the cast creates a temporary, i.e. a copy, which is a rvalue.

//B(std::function<void(std::string&, int)> &&f) : A(f) { } --> fail on casting error, why ?
B(std::function<void(std::string&, int)> &&f) : A((decltype(f)) f) { }

Although f is bound to a rvalue, the f itself since it has a name is a lvalue. Casting it to rvalue (decltype(f)) f)) will convert it back to a rvalue. This is the thing that std::move does.

Upvotes: 1

Andrey Semashev
Andrey Semashev

Reputation: 10614

The problem is that the A's and B's constructors expect an rvalue reference, while you're passing an lvalue. Let's consider B's constructor:

B(std::function<void(std::string&, int)> &&f) : A((decltype(f)) f) { }

Since the argument f is declared as an rvalue reference, it can only bind to an rvalue on the caller side. In other words, passing an lvalue as the argument to the constructor is illegal.

However, this argument in the context of the function body (including the constructor's initializer list) f is an lvalue. Which means it cannot be passed to A's constructor (which also expects an rvalue) directly. What you're doing by the (decltype(f)) cast is casting f to rvalue reference. The more conventional way of doing this is using std::move:

B(std::function<void(std::string&, int)> &&f) : A(std::move(f)) { }

By calling std::move you cast the f lvalue to an rvalue reference without copying.

In other places, you have the same problem - you're trying to pass an lvalue as an rvalue argument. Use std::move to fix that as well.

Now, to this part:

// this works for some reason, even though C c'tor expect std::function ... why ? 
C c([=](std::string&a, int b) {
    printf("%s %d\n", a.c_str(), b);
});

This works because std::function has an implicit converting constructor from a function object, which a lambda function is. This code implicitly constructs a temporary std::function<void(std::string&, int)> object, which copies the lambda to its internal storage. That temporary is then passed to C's constructor (assuming that copy elision takes place, otherwise a copy of that temporary is passed to the constructor).

Upvotes: 1

Klaus
Klaus

Reputation: 25623

If you get an rvalue ref and want to pass this parameter as parameter to any function you call, you have to use std::move to keep it as an rvalue ref.

An explanation why and how std::move must be used, can you find here

BTW: I have no idea why you need to have revalue ref in your functions/constructors but that is something which might be useful in your real code.

Your code will be something like that:

class A {
public:
    A(std::function<void(std::string&, int)> &&f): _f(std::move(f)) { }

    std::function<void(std::string&,int)> _f;
};

class B : public A {
public:
    B(std::function<void(std::string&, int)> &&f) : A(std::move(f)) { }
};


class C {
public:
    // ATTENTION: Here you use not an rvalue ref, so you create a copy
    // which later will be moved. Can be ok, dependent on what you want
    // to achieve
    C(std::function<void(std::string&,int)> f):b( new B(std::move(f))) { }
    B *b;
};

int main() {
    C c([=](std::string&a, int b) {
        printf("%s %d\n", a.c_str(), b);
    });
}

Upvotes: 3

Jarod42
Jarod42

Reputation: 217573

in: B(std::function<void(std::string&, int)> &&f) : A((decltype(f)) f) { }

as f has a name, it is a lvalue, but A's constructor expects a rvalue.

(decltype(f)) f here is equivalent to std::move(f).

Upvotes: 3

Related Questions