I always read that std::forward is only for use with template parameters. However, I was asking myself why. See the following example:
void ImageView::setImage(const Image& image){
_image = image;
}
void ImageView::setImage(Image&& image){
_image = std::move(image);
}
Those are two functions which basically do the same; one takes an l-value reference, the other an r-value reference. Now, I thought since std::forward is supposed to return an l-value reference if the argument is an l-value reference and an r-value reference if the argument is one, this code could be simplified to something like this:
void ImageView::setImage(Image&& image){
_image = std::forward(image);
}
Which is kind of similar to the example cplusplus.com mentions for std::forward (just without any template parameters). I'd just like to know, if this is correct or not, and if not why.
I was also asking myself what exactly would be the difference to
void ImageView::setImage(Image& image){
_image = std::forward(image);
}
You cannot use std::forward without explicitly specifying its template argument. It is intentionally used in a non-deduced context.
To understand this, you need to really understand how forwarding references (T&& for a deduced T) work internally, and not wave them away as "it's magic." So let's look at that.
template <class T>
void foo(T &&t)
{
bar(std::forward<T>(t));
}
Let's say we call foo like this:
foo(42);
42 is an rvalue of type int.
T is deduced to int.
The call to bar therefore uses int as the template argument for std::forward.
The return type of std::forward<U> is U && (in this case, that's int &&) so t is forwarded as an rvalue.
Now, let's call foo like this:
int i = 42;
foo(i);
i is an lvalue of type int.
Because of the special rule for perfect forwarding, when an lvalue of type V is used to deduce T in a parameter of type T &&, V & is used for deduction. Therefore, in our case, T is deduced to be int &.
Therefore, we specify int & as the template argument to std::forward. Its return type will therefore be "int & &&", which collapses to int &. That's an lvalue, so i is forwarded as an lvalue.
Summary
Why this works with templates is when you do std::forward<T>, T is sometimes a reference (when the original is an lvalue) and sometimes not (when the original is an rvalue). std::forward will therefore cast to an lvalue or rvalue reference as appropriate.
You cannot make this work in the non-template version precisely because you'll have only one type available. Not to mention the fact that setImage(Image&& image) would not accept lvalues at all—an lvalue cannot bind to rvalue references.
I recommend reading "Effective Modern C ++" by Scott Meyers, specifically:
Item 23: Understand std::move and std::forward.
Item 24: Distinguish universal references for rvalue references.
From a purely technical perspective, the answer is yes: std::forward
can do it all. std::move isn’t necessary. Of course, neither function
is really necessary, because we could write casts everywhere, but I
hope we agree that that would be, well, yucky. std::move’s attractions
are convenience, reduced likelihood of error, and greater clarity.
rvalue-reference
This function accepts rvalues and cannot accept lvalues.
void ImageView::setImage(Image&& image){
_image = std::forward(image); // error
_image = std::move(image); // conventional
_image = std::forward<Image>(image); // unconventional
}
Note first that std::move requires only a function argument, while std::forward requires both a function argument and a template type argument.
Universal references (forwarding references)
This function accepts all and does perfect forwarding.
template <typename T> void ImageView::setImage(T&& image){
_image = std::forward<T>(image);
}
You have to specify the template type in std::forward.
In this context Image&& image is always an r-value reference and std::forward<Image> will always move so you might as well use std::move.
Your function accepting an r-value reference cannot accept l-values so it is not equivalent to the first two functions.
Related
I'm currently learning perfect forwarding in c++ and I came across something that confused me. Pretty sure it's something stupid. When I used std::forward on a lvalue, it used the rvalue function. This is a pretty bad explanation so I'm just gonna show the code.
#include <iostream>
void check(int&& other) {
std::cout << "Rvalue" << std::endl;
}
void check(int& other) {
std::cout << "Lvalue" << std::endl;
}
void call(int& other) {
check(std::forward<int>(other));
}
int main()
{
int i = 4;
call(i);
}
This outputs "Rvalue". Please help me understand why.
"Perfect forwarding" is used in templates, i.e.:
template<typename T>
void func(T && arg)
{
func2(std::forward<T>(arg));
}
Outside of template context the end result drastically changes. All that std::forward does is return static_cast<T &&>, so your call function becomes nothing more than:
void call(int& other) {
check(static_cast<int &&>(other));
}
Hence you get an rvalue. The reason this works differently in templates is because a && template parameter is a forwarding reference (a fancy term for either an lvalue or an rvalue-deduced reference, depending on what gets dropped in that parameter), and because of reference collapsing rules. Briefly, when used in a template context, the end result is:
T gets deduced as either an lvalue or an rvalue reference, depending on what the parameter is.
The result of the static_cast<T &&> is an lvalue reference, if T is an lvalue reference, or an rvalue reference if T is an rvalue reference, due to reference collapsing rules.
The end result is that the same kind of a reference gets forwarded. But this only works in template context, since it requires both forwarding reference semantics and reference collapsing rules to work just right.
std::forward() isn't exactly magic. Which is one of the reasons one has to give it the appropriate type with the appropriate reference-category (rvalue-reference, lvalue-reference, no reference).
Normally, you get the type from the template-argument. If it's an auto&&-argument (C++14 lambda, C++20 abbreviated template or use of concept), one uses decltype() to get it from the function-argument.
Manually specifying it works, but goes against the spirit of using the function. std::move() or the argument itself is much easier to use in that case, depending on the template-argument you specify.
And due to how reference-collapsing and std::forward() are defined, Type and Type&& both result in an rvalue-reference, while Type& results in an lvalue-reference.
If I define a function which accepts an rvalue reference parameter:
template <typename T>
void fooT(T &&x) {}
I can call it, using GCC 4.5, with either a, ar, or arr:
int a, &ar = a, &&arr = 7;
fooT(a); fooT(ar); fooT(arr);
However, calling a similar, non-template function,
void fooInt(int &&x) {}
with any of those three arguments will fail. I was preparing to strengthen my knowledge of forward, but this has knocked me off course. Perhaps it's GCC 4.5; I was surprised to find that the first example from A Brief Introduction to Rvalue References also gives a compile error:
A a;
A&& a_ref2 = a; // an rvalue reference
The behavior of deduction in template parameters is unique, and is the reason your template version works. I've explained exactly how this deduction works here, in the context of another question.
Summarized: when the argument is an lvalue, T is deduced to T&, and T& && collapses to T&. And with the parameter at T&, it is perfectly valid to supply an lvalue T to it. Otherwise, T remains T, and the parameter is T&&, which accepts rvalues arguments.
Contrarily, int&& is always int&& (no template deduction rules to coerce it to something else), and can only bind to rvalues.
In addition to GMan's correct answer A Brief Introduction to Rvalue References has an incorrect example because it was written prior to a language change which outlawed:
A a;
A&& a_ref2 = a; // an rvalue reference (DISALLOWED in C++11)
Despite this change in the language, the main uses cases described in the article (move and forward) are still explained correctly in the article.
Update: Oh, and the same article was originally published here with (imho) slightly better formatting.
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.
For a template default case, I need a function that does nothing but simply forwards whatever it receives as an argument. Specifically, references, const-ness etc. should be preserved. Writing transparent(/* something */) should be completely equivalent to writing /* something */.
Is the following function definition correct for achieving that purpose?
template <class S>
decltype(auto) transparent (S && s) { return std::forward<S> (s); }
Add a constexpr and it's as good as it gets. prvalues will yield xvalues; However, that's not improvable since one cannot distinguish prvalues and xvalues using overload resolution.
You won't be able to properly forward 0 as a null pointer constant or string literals as initializers, but the only way to achieve that would be a macro (which is not what you're going for).
Your implementation is fine, but here are some things to come to mind:
If a call to transparent () passes an rvalue std::string, then is
deduced to std::string, and std::forward ensures that an rvalue
reference is return.
If a call to transparent () passes a const lvalue std::string, then S
is deduced to const std::string&, and std::forward ensures that a
const lvalue reference will return
If a call to transparent () passes a non-const lvalue std::string,
then S is deduced to std::string&, and std::forward ensures that a
non-const lvalue reference will return
But why do you need this? A common use to
std::forward in templates is to the a warpper like that:
template<class T>
void wrapper(T&& arg)
{
foo(std::forward<T>(arg)); // Forward a single argument.
}
Given the following function template from "The C++ Programming language 4th edition":
template <typename TT, typename A>
unique_ptr<TT> make_unique(int i, A && a)
{
return unique_ptr<TT>{new TT{ i, std::forward<A>(a) }};
}
I find it difficult to understand what that actually does,
a is definitely an rvalue and therefore the make_unique function
seem to allocate its content on the heap and holding that address in a unique_ptr so we won't have to worry about deleting it. but, what does the standard library forward function does? (I guess it has something to do with a being rvalue) I tried reading at C++ documentation but I don't seem to understand that properly.
would love to get a good explanation from a more experienced C++ programmer.
thanks!
Hmmm... I'm pretty sure this isn't given as a work-around implementation of the future std::make_unique, but anyway, what the function does is pretty easy to understand, though it requires you to have prior knowledge of new C++11 features.
template <typename TT, typename A>
unique_ptr<TT> make_unique(int i, A && a)
{
return unique_ptr<TT>{new TT{ i, std::forward<A>(a) }};
}
First of all make_unique is a function template, I really hope you already know that, as the following would require that you have at least the most basic knowledge on what templates does and how templates work.
Now to the non-trivial parts. A && a there is a function parameter. Specifically, a is the function parameter whose type is A&& which is an r-value reference. With its type being a template type parameter, we can deduce its type from whatever the caller passes as an argument to a. Whenever we have r-value reference and argument type deduction, special deduction rules and reference collapsing kicks-in and we have a so-called "universal reference" which is particularly useful for perfect forwarding functions.
Whenever we have a universal reference (a in our case), we will almost always want to preserve its original "l-valueness" or "r-valueness" whenever we want to use them. To have this kind of behavior, we should almost always use std::forward (std::forward<A>(a)). By using std::forward, a variable originally passed as an l-value remains an l-value and a variable originally passed as an r-value remains an r-value.
After that, things are just simple
return unique_ptr<TT>{new TT{ i, std::forward<A>(a) }};
Notice the use of the braces. Instead of using parentheses, it is using C++11's uniform initialization syntax of calling constructors. With new TT{ i, std::forward<A>(a) }, you are dynamically allocating an object of type TT with the given parameters inside the braces. With unique_ptr<TT>{new TT{ i, std::forward<A>(a) }};, you are creating a unique_ptr<TT> whose parameter is the one returned by the dynamic allocation. The unique_ptr<TT> object now then returned from the function.
Due to template argument deduction and reference collapsing rules you cannot know if a is a rvalue reference or a lvalue reference. std::forward passes the argument to the TT contrustor exactly as it was passed to make_unique. Scott Meyers calls A&& a universal reference, because it can be a lvalue ref or an rvalue ref, depended on what is passed to make_unique.
If you pass an rvalue Foo to make_unique, std::forward passes an rvalue reference.
If you pass an lvalue Foo to make_unique, std::forward passes an lvalue reference.
make_unique(1, Foo()); // make_unique(int, A&&) -> rvalue ref
Foo f;
make_unique(1, f); // make_unique(int, A&&&) -> make_unique(int, A&) -> lvalue ref
make_unique(1, std::move(f)); // make_unique(int, A&&&&) -> make_unique(int, A&&) -> rvalue ref