Does std::function support perfect forwarding of arguments? I decided to test it:
struct Widget
{
void operator()(int const&)
{
std::cout << "lvalue";
}
void operator()(int&&)
{
std::cout << "rvalue";
}
};
std::function<void(int)> f{Widget()};
int x = 5;
f(x);
Guess which operator() gets called - the one taking rvalue reference. It seems that it is by design. What is the rationale behind this behavior?
Yes and no. Yes, the arguments are forwarded. But no, overload resolution is not done based on the arguments you provide at the point of the call - it's done based on the template arguments to std::function.
std::function<void(int)> means you have a callable that takes an int, which implicitly is an int rvalue. Calling Widget with an rvalue of type int prefers the int&& overload over the int const& overload, which is why that one is selected. It doesn't matter how you invoke the actual function object, once you selected that you want void(int) - that's the preferred overload.
The actual call operator behaves as if:
struct widget_function {
void operator()(int arg) const {
// ~~~~~ ~~~~
w_(std::forward<int>(arg));
// ~~~
}
Widget w_;
};
The underlined portions come from the signature you provide to std::function. You can see that both f(x) and f(5) end up calling the same operator() from Widget.
In short, std::function<Sig> type erases one single overload that satisfies Sig. It does not type erase an entire overload set. You asked for void(int), so you get void(int) and only void(int).
Related
I would like to go over an example using std::forward because sometimes I can make it work and some other times I can’t.
This is the code
void f(int&& int1, int&& int2){
std::cout << "f called!\n";
}
template <class T>
void wrapper(T&& int1, T&& int2){
f(std::forward<T>(int1), std::forward<T>(int2));
}
int main(){
int int1 = 20;
int int2 = 30;
int &int3 = int1;
wrapper(int1, int2);
}
I am passing int 1 and int 2. These are lvalues. They are silently converted to &int1, &int2. These are converted using &&. But reference collapsing keeps them just &int1, &int2.
f takes && parameters
If I pass simply int1 and int2 as they are I am passing &int1, &int2. This does not work.
So I pass std::forward(int1) std::forward(int2).It should be the same as using static_cast<T&&>. Because of this, thanks to referencing collapsing I can pass to every function f (theoretically even one that accepts only l-value references).
My code does not compile and my logical reasoning has probably some contradictions.
candidate function not viable: no known conversion from 'int' to 'int &&' for 1st argument
void f(int&& int1, int&& int2){
How on earth did I get a simple int after using all these ampersands?
Additional question: My compiler asks me to use wrapper<int &> instead of only wrapper(some parameters). Can I just leave it like in my code, or I need to manually put wrapper<int &> (this is what my compiler is asking me to add). Why do I need <int &> int this case?
The whole problem stems from the forwarding references using same symbols as rvalue ones, but not being the same.
Take the following code:
template<typename T>
void f(T&& t)
{
//whatever
}
In this case T&& is a forwarding reference. It is neither T&& in the sense of rvalue-reference (reference to temporary), nor is it T&. Compiler deduces that at compile time. Notice though, it does not have type specified either, it's a template paremeter. Only in case of those parameters the forwarding reference semantics applies (another way of writing it down is a auto&& in lambdas, but the deduction works the same way).
Thus when you call int x= 3; f(x); you're effectively calling f(int&). Calling f(3) calls effectively f(int&&) though.
void g(int&& arg)
arg is and rvalue reference to int. Because the type is specified, it's not a template argument! It's always an rvalue reference.
Thus, for your case
void f(int&& int1, int&& int2){
std::cout << "f called!\n";
}
template <class T>
void wrapper(T&& int1, T&& int2){
f(std::forward<T>(int1), std::forward<T>(int2));
}
int main(){
int int1 = 20;
int int2 = 30;
int &int3 = int1;
wrapper(int1, int2); //call wrapper(int&, int&);
//Then f(std::forward<T>(int1), std::forward<T>(int2));-> f(int&, int&), because they are lvalues! Thus, not found, compilation error!
}
Live demo: https://godbolt.org/z/xjTnjcqj8
forward is used to convert the parameter back to the "valueness" it had when passed to the function. We can see how this works using this example
void foo(int&) { std::cout << "foo(int&)\n"; }
void foo(int&&) { std::cout << "foo(int&&)\n"; }
template <typename T> void wrapper(T&& var) { foo(std::forward<T>(var)); }
int main()
{
int bar = 42;
wrapper(bar);
wrapper(42);
}
which outputs
foo(int&)
foo(int&&)
So, when you pass wrapper an lvalue, forward will forward that along, and the lvalue accepting overload of foo is called. When you pass an rvalue to wrapper, forward will convert var back into an rvalue1 and the rvalue overload of foo is called.
Since your f function only accepts rvalues, that means your wrapper function will also only work for rvalues. You are basically just trying to do f(int1, int2) in main, and that wont work.
The reason you get the error message no known conversion from 'int' to 'int &&' is that it is trying to tell you that there is no conversion from an lvalue int into a reference to an rvalue int.
1: This is needed because as a named variable, it is an lvalue, even if it is a reference to an rvalue.
I'll start off with something mentioned in the comments, and that is that having only one template parameter here relates the two parameters and interferes with the usual deduction process. Normally when forwarding, you deduce a type for each forwarded parameter independently. In this case, it will still work, but only because the call site passes two things with the same type and the same value category (lvalue, rvalue, etc.).
Here's what a typical wrapper would look like, truly forwarding the arguments independently:
template <class T, class T2>
void wrapper(T&& int1, T2&& int2){
f(std::forward<T>(int1), std::forward<T2>(int2));
}
With that out of the way, I'll move on to the intuitive reason your code doesn't compile. If forwarding is done correctly, the wrapper function's existence won't change anything. By the above reasoning, your wrapper function meets this criteria for this particular call. Let's see what happens when it's gone:
void f(int&& int1, int&& int2){
std::cout << "f called!\n";
}
int main(){
int int1 = 20;
int int2 = 30;
int &int3 = int1;
f(int1, int2);
}
You might be able to spot the error more clearly now. f takes rvalues, but it's being given lvalues. The wrapper preserves the value category of its arguments, so they're still lvalues when handed to f. To fix this with the wrapper, it's the same as without—pass rvalues:
int main(){
f(20, 30);
// Alternatively: f(std::move(int1), std::move(int2));
}
Now, reviewing the points made:
I am passing int 1 and int 2. These are lvalues. They are silently converted to &int1, &int2. These are converted using &&. But reference collapsing keeps them just &int1, &int2.
Don't think about the variables themselves here, but the types. Because you pass in two lvalue ints, T is deduced to be int&. Following that, the actual parameter types, T&&, are then int& as well because of reference collapsing. Thus, you have a function stamped out with two int& parameters. In the forward calls, T is int&, so its reference-collapsed return type is again int&. Thus, the call expressions have the type int and are lvalues (specifically because the return type is int&—the language calls that out as an explicit rule).
As a side note, &int1 isn't clear to me because its C++ meaning is taking the address of int1, an entirely irrelevant meaning here. I think what you're trying to say is that it's an lvalue or that a parameter's type is an lvalue reference.
If I pass simply int1 and int2 as they are I am passing &int1, &int2. This does not work.
This is true for the the reasons discussed earlier. The parameters are themselves lvalues, so a function taking rvalues won't accept them.
So I pass std::forward(int1) std::forward(int2).It should be the same as using static_cast<T&&>. Because of this, thanks to referencing collapsing I can pass to every function f (theoretically even one that accepts only l-value references).
Yes, this is the point of forwarding. It lets you preserve the value category of the arguments given to the wrapper, so the same template can be used to pass along arguments to a function regardless of which value categories it accepts. It's up to the caller of the wrapper to provide the correct value category in the first place and then the wrapper simply promises not to mess with it.
How on earth did I get a simple int after using all these ampersands?
This is just how the type is displayed in the error message. Although the parameter is an int&, the expression has the type int. Remember that an expression has both a type and a value category and that they're separate properties. For example, you can have an rvalue reference parameter int&& x and the expression x will still be an int and an lvalue.
Does declaring something like the following
void foo(int x) { std::cout << "foo(int)" << std::endl; }
void foo(const int &x) { std::cout << "foo(const int &)" << std::endl; }
ever make sense? How would the caller be able to differentiate between them? I've tried
foo(9); // Compiler complains ambiguous call.
int x = 9;
foo(x); // Also ambiguous.
const int &y = x;
foo(y); // Also ambiguous.
The intent seems to be to differenciate between invocations with temporaries (i.e. 9) and 'regular' argument passing. The first case may allow the function implementation to employ optimizations since it is clear that the arguments will be disposed afterwards (which is absolutely senseless for integer literals, but may make sense for user-defined objects).
However, the current C++ language standard does not offer a way to overload specifically for the 'l/r-valueness' of arguments - any l-value being passed as argument to a function can be implicitly converted to a reference, so the ambiguity is unavoidable.
C++11 introduces a new tool for a similar purpose — using r-value references, you can overload as follows
void foo(int x) { ... }
void foo(const int &&x) { ... }
... and foo(4) (a temporary, r-value passed as argument) would cause the compiler to pick the second overload while int i = 2; foo(i) would pick the first.
(note: even with the new toolchain, it is not possible to differentiate between the cases 2 and 3 in your sample!)
You could do this with a template:
template<typename T> void foo(T x) { ... }
Then you can call this template by value or by reference:
int x = 123;
foo<int>(x); // by value
foo<int const&>(x); // by refernce
How would the caller be able to differentiate between them?
It cannot be differentiated in this case. Both the overloaded functions have the same type of primitive data type as the argument. And taking by reference doesn't count for a different type.
You can use static_cast to explicitly select the overload to be called:
#include <iostream>
void foo(int x) { std::cout << "foo(int)" << std::endl; }
void foo(const int &x) { std::cout << "foo(const int &)" << std::endl; }
int main()
{
int x = 0;
auto f1 = static_cast< void(*)(int) >(foo);
f1(x);
auto f2 = static_cast< void(*)(const int&) >(foo);
f2(x);
}
However, you should ask yourself why you provided those two overloads in the first place. Either you are fine with making a copy or you are not. Both at the same time? Why? Also making it necessary for the caller to explicitly select the overload defeats the purpse of function overloading. If you really want that consider to supply two functions instead:
void foo_copying(int x) { std::cout << "foo(int)" << std::endl; }
void foo_non_copying(const int &x) { std::cout << "foo(const int &)" << std::endl; }
Not in C++. Functional languages such as Erlang and Haskell get closer by allowing you to specify function overloads based on parameter value, but most imperative languages including C++ require overloading based on method signature; that is, the number and type of each parameter and the type of the return value.
The const keyword in the signature defines not the type of the parameter, but its mutability within the function; a "const" parameter will generate a compiler error if modified by the function or passed by reference to any function that doesn't also use const.
The compiler can't.
Both definitions of foo can be used for all 'variants' of int.
In the first foo, a copy of the int is made. Copying an int is always possible.
In the second foo, a reference to a const int is passed. Since any int can be cast to a const int, a reference to it can be passed as well.
Since both variants are valid in all cases, the compiler can't choose.
Things become different if you e.g. use the following definition:
void foo (int &x);
Now calling it with foo(9) will take the first alternative, since you can't pass 9 as a non-const int reference.
Another example, if you replace int by a class where the copy constructor is private, then the caller can't make a copy of the value, and the first foo-variant will not be used.
An example here for std::forward,
// forward example
#include <utility> // std::forward
#include <iostream> // std::cout
// function with lvalue and rvalue reference overloads:
void overloaded (const int& x) {std::cout << "[lvalue]";}
void overloaded (int&& x) {std::cout << "[rvalue]";}
// function template taking rvalue reference to deduced type:
template <class T> void fn (T&& x) {
overloaded (x); // always an lvalue
overloaded (std::forward<T>(x)); // rvalue if argument is rvalue
}
int main () {
int a;
std::cout << "calling fn with lvalue: ";
fn (a);
std::cout << '\n';
std::cout << "calling fn with rvalue: ";
fn (0);
std::cout << '\n';
return 0;
}
Output:
calling fn with lvalue: [lvalue][lvalue]
calling fn with rvalue: [lvalue][rvalue]
mentions that
the fact that all named values (such as function parameters) always
evaluate as lvalues (even those declared as rvalue references)
Whereas, the typical move constructor looks like
ClassName(ClassName&& other)
: _data(other._data)
{
}
which looks like _data(other._data) should invoke the move constructor of _data's class. But, how is it possible without using std::forward? In other words, shouldn't it be
ClassName(ClassName&& other)
: _data(std::forward(other._data))
{
}
?
Because, as pointed out in std:forward case,
all then named values should evaluate as lvalue
I more and more like C++ because of the depth of issue like this and the fact that the language is bold enough to provide such features :) Thank you!
A typical move constructor looks like this (assuming it is explicitly implemented: you might want to prefer = default):
ClassName::ClassName(ClassName&& other)
: _data(std::move(other._data)) {
}
Without the std::move() the member is copied: since it has a name other is an lvalue. The object the reference is bound to is an rvalue or an object considered as such, however.
std::forward<T>(obj) is always used with an explicit template argument. In practice the type is that deduced for a forwarding reference. These look remarkably like rvalue references but are something entirely different! In particular, a forwarding reference may refer to an lvalue.
You may be interested in my Two Daemons article which describes the difference in detail.
std::forward should be used with a forwarding reference.
std::move should be used with an rvalue reference.
There is nothing particular about constructors. The rules apply the same to any function, member function or constructor.
The most important thing is to realize when you have a forwarding reference and when you have an rvalue reference. They look similar but are not.
A forwarding reference is always in the form:
T&& ref
for T some deduced type.
For instance, this is a forwarding reference:
template <class T>
auto foo(T&& ref) -> void;
All these are rvalue references:
auto foo(int&& ref) -> void; // int not deduced
template <class T>
auto foo(const T&& ref); // not in form `T&&` (note the const)
template <class T>
auto foo(std::vector<T>&& ref) -> void; // not in form `T&&`
template <class T>
struct X {
auto foo(T&& ref) -> T; // T not deduced. (It was deduced at class level)
};
For more please check this excellent in-depth article by Scott Meyers with the note that when the article was written the term "universal reference" was used (actually introduced by Scott himself). Now it is agreed that "forwarding reference" better describes it's purpose and usage.
So your example should be:
ClassName(ClassName&& other)
: _data(std::move(other._data))
{
}
as other is an rvalue reference because ClassName is not a deduced type.
This Ideone example should make things pretty clear for you. If not, keep reading.
The following constructor accepts Rvalues only. However, since the argument "other" got a name it lost its "rvalueness" and now is a Lvalue. To cast it back to Rvalue, you have to use std::move. There's no reason to use std::forward here because this constructor does not accept Lvalues. If you try to call it with a Lvalue, you will get compile error.
ClassName(ClassName&& other)
: _data(std::move(other._data))
{
// If you don't use move, you could have:
// cout << other._data;
// And you will notice "other" has not been moved.
}
The following constructor accepts both Lvalues and Rvalues. Scott Meyers called it "Universal Rerefences", but now it's called "Forwarding References". That's why, here, it's a must to use std::forward so that if other was an Rvalue, _data constructor will get called with an Rvalue. If other was an Lvalue, _data will be constructed with an Lvalue. That's why it's called perfect-forwarding.
template<typename T>
ClassName(T&& other)
: _data(std::forward<decltype(_data)>(other._data))
{
}
I've tried to use your constructors as an example so you could understand, but this is not specific to constructors. This applies to functions as well.
With the first example tho, since your first constructor only accepts Rvalues, you could perfectly use std::forward instead, and both would do the same thing. But it's best not to do it, because people may think that your constructor accepts a forwarding reference, when it actually doesn't.
Are these valid usage of move and forward?
Are f3 and f4 the same?
Is it dangerous to do so?
Thank you!
#include <utility>
class A {};
A f1() {
A a;
return a; // Move constructor is called
}
A f2(A&& a) {
return a; // Copy constructor is called, which is what I try to avoid.
}
A f3(A&& a) {
return std::forward<A&&>(a); // Move constructor is called
}
A f4(A&& a) {
return std::move(a); // Move constructor is called
}
std::forward exists because of a quirk in how && works under type deduction.
Under type deduction, the T in T&& will bind to one of 3 possibilities. If being deduced from an lvalue int&, T will bind to int&. Then int& && is just a int&. If being deduced from an lvalue int const&, T will bind to int const&, and int const& && is int const&. If being deduced from an rvalue int of some kind, T will bind to int, and int&& is int&&.
std::forward is a utility function to reverse that map. The three pertinent signatures of std::forward<> are: T& std::forward<T&>(T&) or T const& std::forward<T const&>(T const&) or T&& std::forward<T>(T&&)
All of this ends up being exceedingly useful when doing the technique known as "perfect forwarding", where you use T&&t in a type deduction context, then std::forward<T>(t) to pass on the "same type" as was deduced from to another call.
Note that there are a few simplifying lies above. There are is also the possibility of T const&& which is pretty obscure type-wise, as an example. I probably glossed over some details of how the type deduction works, and the terms rvalue and lvalue don't fully reflect the full 5-fold (or is it 6?) different kinds of variable values in C++11.
Use std::forward with a universal reference, i.e. a template <typename T> ... T&&.
Use std::move with an rvalue reference (like your A&&).
So both f1 and f4 are plausible solutions. They do different things, so you have to decide which one you want.
Do not use f2 or f3.
For your example, they will do the same thing, but it is idiomatic to use std::move
A f(A&& a) {
// use std::move(a)
}
A slightly different case is with function templates
template<typename A>
A f(A&& a) {
// use std::forward<A>(a)
}
The difference is that the second version can receive both lvalues and rvalues (Scott Meyers named them "universal references"), whereas the first version can only receive rvalues.
Does declaring something like the following
void foo(int x) { std::cout << "foo(int)" << std::endl; }
void foo(const int &x) { std::cout << "foo(const int &)" << std::endl; }
ever make sense? How would the caller be able to differentiate between them? I've tried
foo(9); // Compiler complains ambiguous call.
int x = 9;
foo(x); // Also ambiguous.
const int &y = x;
foo(y); // Also ambiguous.
The intent seems to be to differenciate between invocations with temporaries (i.e. 9) and 'regular' argument passing. The first case may allow the function implementation to employ optimizations since it is clear that the arguments will be disposed afterwards (which is absolutely senseless for integer literals, but may make sense for user-defined objects).
However, the current C++ language standard does not offer a way to overload specifically for the 'l/r-valueness' of arguments - any l-value being passed as argument to a function can be implicitly converted to a reference, so the ambiguity is unavoidable.
C++11 introduces a new tool for a similar purpose — using r-value references, you can overload as follows
void foo(int x) { ... }
void foo(const int &&x) { ... }
... and foo(4) (a temporary, r-value passed as argument) would cause the compiler to pick the second overload while int i = 2; foo(i) would pick the first.
(note: even with the new toolchain, it is not possible to differentiate between the cases 2 and 3 in your sample!)
You could do this with a template:
template<typename T> void foo(T x) { ... }
Then you can call this template by value or by reference:
int x = 123;
foo<int>(x); // by value
foo<int const&>(x); // by refernce
How would the caller be able to differentiate between them?
It cannot be differentiated in this case. Both the overloaded functions have the same type of primitive data type as the argument. And taking by reference doesn't count for a different type.
You can use static_cast to explicitly select the overload to be called:
#include <iostream>
void foo(int x) { std::cout << "foo(int)" << std::endl; }
void foo(const int &x) { std::cout << "foo(const int &)" << std::endl; }
int main()
{
int x = 0;
auto f1 = static_cast< void(*)(int) >(foo);
f1(x);
auto f2 = static_cast< void(*)(const int&) >(foo);
f2(x);
}
However, you should ask yourself why you provided those two overloads in the first place. Either you are fine with making a copy or you are not. Both at the same time? Why? Also making it necessary for the caller to explicitly select the overload defeats the purpse of function overloading. If you really want that consider to supply two functions instead:
void foo_copying(int x) { std::cout << "foo(int)" << std::endl; }
void foo_non_copying(const int &x) { std::cout << "foo(const int &)" << std::endl; }
Not in C++. Functional languages such as Erlang and Haskell get closer by allowing you to specify function overloads based on parameter value, but most imperative languages including C++ require overloading based on method signature; that is, the number and type of each parameter and the type of the return value.
The const keyword in the signature defines not the type of the parameter, but its mutability within the function; a "const" parameter will generate a compiler error if modified by the function or passed by reference to any function that doesn't also use const.
The compiler can't.
Both definitions of foo can be used for all 'variants' of int.
In the first foo, a copy of the int is made. Copying an int is always possible.
In the second foo, a reference to a const int is passed. Since any int can be cast to a const int, a reference to it can be passed as well.
Since both variants are valid in all cases, the compiler can't choose.
Things become different if you e.g. use the following definition:
void foo (int &x);
Now calling it with foo(9) will take the first alternative, since you can't pass 9 as a non-const int reference.
Another example, if you replace int by a class where the copy constructor is private, then the caller can't make a copy of the value, and the first foo-variant will not be used.