Reputation: 877
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
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
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
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
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