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.
Related
I understand that i is an lvalue and 5 is an rvalue indeed(i.e. 5 is unnamed). I know how to use template <typename T> void fun(T&& x); and when to use it, but I still can't wrap my head around why fun(i) is deduced asfun<int&> other than fun<int> since i is the type of int and not a reference indeed?
I know there are many questions about rvalue\lvalue and forward reference on SO, but my question is quite different. I have browsed many related answers, nobody has explained it in detail indeed.
Here is the related code snippets:
template <typename T>
void fun(T&& x);
int i;
fun(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
fun(5); // deduce<int>(int&&)
why fun(i) is deduced as fun<int&> other than fun<int> since i is the type of int and not a reference indeed?
The deduction result depends on not only argument's type, but also argument's value category.
As the result, if the argument is lvalue, T would be deduced as int&, the function parameter is int& too. If the argument is rvalue, T would be deduced as int, the function parameter is int&&.
Note that both i and 0 are of type int. If value category is not considered then we can't distinguish them (and preserve the value category of function argument).
The following code compiles with c++11 or higher
#include <iostream>
#include <cstdlib>
#include <cstdio>
#define p 1
// p1: prototype 1
template <class Function, class... Args>
void addv(Function&& f, Args&... args) {
std::cout << f(args...) << std::endl;
}
// p2: prototype 2
template <class Function, class... Args>
void addv(Function& f, Args&... args) {
std::cout << f(args...) << std::endl;
}
int add(int& a, int& b) {
return a+b;
}
class Adder {
public:
int operator () (int& a, int&b) {
return a+b;
}
};
int main() {
int a = 2;
int b = 1;
Adder adder;
addv<int (int&,int&),int,int>(add,a,b); // uses p1 OR p2
addv<Adder,int,int>(Adder(),a,b); // uses p1
addv<Adder,int,int>(adder,a,b); // uses p2
}
If prototype 2 is removed, and this is compiled, the following error happens:
exp.cpp:36:36: error: no matching function for call to ‘addv<Adder, int, int>(Adder&, int&, int&)’
addv<Adder,int,int>(adder,a,b); // uses p2
^
exp.cpp:9:10: note: candidate: template<class Function, class ... Args> void addv(Function&&, Args& ...)
void addv(Function&& f, Args&... args) {
^~~~
exp.cpp:9:10: note: template argument deduction/substitution failed:
exp.cpp:36:36: note: cannot convert ‘adder’ (type ‘Adder’) to type ‘Adder&&’
addv<Adder,int,int>(adder,a,b); // uses p2
^
Why adder cannot be converted from a lvalue to a rvalue implicitly as is needed to have the line addv<Adder,int,int>(adder,a,b); use prototype 1?
Is it possible to explicitly create a rvalue reference of adder to have it correctly match prototype 1?
It can't be converted because the idea with using rvalue and lvalue references in function signatures is exactly to make sure you get one or the other. Not either one.
Normally that is used because if you get an rvalue, you can move it. If you get an lvalue you need to copy it. You can also make sure that a function is only callable with an rvalue, or lvalue.
When passing functions the usual way is to take the parameter by value. That also works with both rvalues and lvalues. This is how it's (almost always?) done in the standard library. Function pointers and Functors are generally very cheap to copy.
If you want a signature that can take both rvalues and lvalues you can use const &.
Also note that since Function&& is a template parameter, it is a forwarding reference. Thats means it will become an rvalue reference or lvalue reference depending on what you pass in.
When you call the function with an explicitly specified parameter though
addv<Adder,int,int>(adder,a,b);
^--this
the template parameter will be deduced to exactly Adder, and your function will then accept only rvalues, since the signature says Function&& -> Adder&&.
The easy way to make the code work is to not explicitly specify the template parameter.
addv(adder,a,b);
Then you can remove prototype 2 and all the function calls will work.
If you really want to or need to specify the parameters, you can use std::move to convert an lvalue to an rvalue at the calling site.
addv<Adder,int,int>(std::move(adder),a,b);
Edit: Convert might be a bit misleading. It's actually a cast. Nothing is changed except the value category.
Your code will compile if you don't explicitly specify template parameters and let template type deduction work it out:
addv(Adder(),a,b); // this is deduced to
// addv<Adder, int, int>(Adder&&, int&, int&)
addv(adder,a,b); // this is deduced to
// addv<Adder&, int, int>(Adder& &&, int&, int&),
// which becomes addv<Adder&, int,int>(Adder&, int&, int&)
// after reference collapsing
So you do want to explicitly specify template parameter, it needs to be
addv<Adder&, int,int>(adder,a,b);
Have a look at Scott Meyers' excellent article on universal reference (and in particular 'reference collapsing'), hope it helps.
With rvalue references, many redundant copies may be elided, but that seems to require me to write the same function multiple times (one for an rvalue reference, one for a const lvalue reference). But the standard library seems to only need to declare some functions once.
For example:
#include <iostream>
#include <tuple>
void foo(int&& x){
x = 2;
}
int main()
{
int x = 1;
foo(x); // compile error
std::make_tuple(x); // ok
std::cout << x << std::endl;
}
Calling foo(x) is a compile error, because I cannot convert implicitly from int to int&&. But I am perplexed as to why std::make_tuple would work. The reference says that it only accepts rvalue reference parameters. It also seems to not make copies when the value passed into it is an ravlue reference, but it would make a copy (as most would expect) when used as in my sample above.
How can I make foo work like this?
The reference says that it only accepts rvalue reference parameters.
No, this is forwarding reference, which could serve as both lvalue reference and rvalue reference, according to the value category of the passed-in argument.
How can I make foo work like this?
The point of declaring a forwarding reference is (1) type deduction is necessary, that means you need to make foo a function template here; (2) the parameter x has the exact form of T&& for the template parameter T. e.g.
template <typename T>
void foo(T&& x){
x = 2;
}
then
int x = 1;
foo(x); // lvalue passed, T is deduced as int&, parameter's type is int&
foo(1); // rvalue passed, T is deduced as int, parameter's type is int&&
Note this is true for std::make_tuple too, even it's using template parameter pack. And better to bear in mind that even forwarding reference looks like rvalue reference but they're different things.
BTW: std::forward is usually used with forwarding reference to preserve the value category of the argument e.g. when forwarding it to other functions.
The difference is a result of a rather subtle overload of the && operator when it is used with a template parameter:
template<typename foo>
void bar(foo &&baz)
This is not an rvalue reference. It is a forwarding reference. When the template gets resolved, baz is going to be either an lvalue or an rvalue reference, depending on the call situation.
This is why you will see the C++ library provide what appears to be a single template, that works in both lvalue and rvalue contexts.
But forwarding references occur in templates only. In a non-template declaration:
void bar(int &&baz)
This is always an rvalue reference, and can only be used in rvalue contexts.
std::make_tuple works because the function doesn't actually take a rvalue reference like foo does. std::make_tuple takes a object in the form of T&& and since T is a template type it makes it a forwarding reference, not a rvalue reference. A forwarding reference can bind to lvalues and rvalues where as a rvalue reference can only take a rvalue. To make foo the same you would need
template<typename T>
void foo(T&& bar)
{
bar = 2;
}
Would this work for you?
#include <iostream>
#include <tuple>
void foo(int&& x){
std::cout<<"rvalue"<<std::endl;
x = 2;
}
void foo(int& x){
std::cout<<"lvalue"<<std::endl;
x = 2;
}
int main()
{
int x = 1;
foo(x); // no compile error anymore
foo(std::move(x)); // now r-value is being used
std::make_tuple(x); // ok
std::cout << x << std::endl;
}
I know that this can be used to perform perfect forwarding:
template <typename A>
void foo(A&&) { /* */ }
This can be used to perform perfect forwarding on a certain type:
template <typename A, std::enable_if_t<std::is_same<std::decay_t<A>, int>::value, int> = 0>
void foo(A&&) { /* */ }
But these are just templates for functions, which means, that these get expanded to some functions, which are then used for every special case in which it might be used. However do these get expanded to:
void foo(A&) and void foo(A&&)
OR
void foo(A&) and void foo(A)
I always thought, it would be the first one, but then I noticed, that in that case, you wouldn't be able to use A const as an argument to the function, which certainly works.
However the second would be ambiguous, if you used a normal non-const lvalue. Does it call foo(A&) or foo(A)?
It's the first one. The second wouldn't make very much sense: there is no A such that A&& is a non-reference type.
If the argument is an lvalue of type cv T, then A is deduced as cv T&. If the argument is an rvalue of type cv T, then A is deduced as cv T and A&& is cv T&&. So when you pass in a const lvalue, the specialization generated is one that can accept a const argument.
They were called originally "Univeral References" by Scott Meyers, and now "Forwarding References".
As you can see, the references part has not changed. You pass in any kind of rvalue, you get a rvalue reference. You pass in any kind of lvalue, and you get a lvalue reference. Life is that simple.
I have the code
void prints_one()
{ cout << "one" << endl; }
int main(int argc, char *argv[])
{
std::function<void()> foo;
foo = prints_one;
foo();
return 0;
}
It works as expected; it prints "one". What I don't know is which assignment operator prototype is being invoked in the assignment and how. Looking at cpp reference, it looks like it probably is this function
template <class Fn> function& operator= (Fn&& fn);
But if that is the prototype being called, I don't understand how a function can bind to a rvalue reference. Thanks!
Update: Thanks all, I'll read up on universal references. In regards to 40two's answer; this code prints that it is an rvalue reference:
template<class Fn>
class Foo {
public:
Foo() {}
Foo& operator=(Fn&& x)
{
std::cout << std::boolalpha << std::is_rvalue_reference<decltype(x)>::value << std::endl;
}
};
void prints_one()
{
cout << "one" << endl;
}
int main(int argc, char *argv[])
{
Foo<void()> bar;
bar = prints_one;
}
This prints true
The C++ standard is meant to be confusing! Otherwise, people like me couldn't play smart just because they were watching while the C++ standard got developed. To this end it was decided that the && notation applied to types shall mean two entirely different although also confusingly related things:
When applied in a non-deduced context, i.e., with a known type T, the notation T&& means that an rvalue reference is being declared.
When applied in a deduced context, e.g., in auto&& x = ... or in function template template <typename T> void f(T&& x) the notation is to mean: determine the type and "referenceness" of the argument. That the deduced type T be of different kinds depending on what was passed as argument:
If a non-const lvalue of type X is being passed, the type T will be deduced as X&, and T&& becomes X& as a reference and an rvalue reference collapse into a reference.
If a const lvalue of type X is being passed, the type T will be deduced as X const&, and T&& becomes X const&, due to the same reference collapsing.
If an rvalue is being passed, the type T will be deduced as X, and T&& becomes X&& as there is nothing to collapse.
The motivations driving the rules on argument deduction is perfect forwarding: the argument should be forwarded to the next layer ideally look like the same kind of type as the argument which got deduced in the first places.
With this in mind, the assignment operator of std::function<...> being called is indeed
template <typename F>
std::function<...>::function(F&& f)
where the argument of the function it just deduced accordingly. In your concrete example the function pointer being passed actually is an rvalue created on the fly by decaying the function type: functions aren't really objects and can't be passed along in C++. To access them other than calling a pointer to the function is being formed. Thus, the argument is an rvalue of type void(*)().
Quoting stuff from here:
T&& isn't always an rvalue reference.
&& in a type declaration sometimes could mean an rvalue reference, but sometimes it means either rvalue reference or lvalue reference.
I'll try to contribute to the above with an example. Consider the following piece of code:
#include <iostream>
#include <type_traits>
using namespace std;
template<class F>
void foo(F&& f)
{
std::cout << std::boolalpha << std::is_rvalue_reference<decltype(f)>::value << std::endl;
}
void prints_one()
{
cout << "one" << endl;
}
int main(int argc, char *argv[])
{
foo(prints_one);
foo([](){ cout << "lambda" << endl; });
return 0;
}
If you run it the output is:
false
true
This mean that although foo takes a T&& as an input parameter, because of the fact that prints_one is an lvalue, input parameter f will be initialized with an lvalue, and consequently it will become an lvalue reference.
On the other hand in the second call of foo we pass a lambda which is really a rvalue reference. As such, the input parameter f is initialized with an rvalue and consequently it becomes an rvalue reference.
Thus, whether input parameter is deduced to an lvalue reference or an rvalue reference depends on input parameter that is passed in foo upon the time it is called.
Update:
Regarding the updated example:
You are defining a template class Foo with an assignment operator Foo<T>::operator=(T&&).
In this case T&& is not a universal reference because there's no type deduction.
It is rather an rvalue reference, hence you are getting true.
This is due to the fact that T is already deducted by the class Foo<T>. Consequently, there can't be type deduction for input parameters of member overloaded operator Foo<T>::operator=(T&&).