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.
}
Related
The standard signature of std::forward is:
template<typename T>
constexpr T&& forward(std::remove_reference_t<T>&) noexcept;
template<typename T>
constexpr T&& forward(std::remove_reference_t<T>&&) noexcept;
Because the parameter type isn't T directly, we should specify the template argument when using std::forward:
template<typename... Args>
void foo(Args&&... args)
{
bar(std::forward<Args>(args)...);
}
However sometimes the template argument is not as simple as Args. auto&& is a case:
auto&& vec = foo();
bar(std::forward<decltype(vec)>(vec));
You can also imagine more complicated template argument types for std::forward. Anyway, intuitively std::forward should know what T is but it actually don't.
So my idea is to omit <Args> and <decltype(vec)> no matter how simple they are. Here is my implementation:
#include <type_traits>
template<typename T>
std::add_rvalue_reference_t<std::enable_if_t<!std::is_lvalue_reference<T>::value, T>>
my_forward(T&& obj)
{
return std::move(obj);
}
template<typename T>
T& my_forward(T& obj)
{
return obj;
}
int main()
{
my_forward(1); // my_forward(int&&)
int i = 2;
my_forward(i); // my_forward(int&)
const int j = 3;
my_forward(j); // my_forward(const int&)
}
When obj is rvalue reference, for example int&&, the first overload is selected because T is int, whose is_lvalue_reference is false;
When obj is lvalue reference, for example const int&, the second overload is selected because T is const int& and the first is SFINAE-ed out.
If my implementation is feasible, why is std::forward still requiring <T>? (So mine must be infeasible.)
If not, what's wrong? And still the question, is it possible to omit template parameter in std::forward?
The problematic case is when you pass something of rvalue reference type but which does not belong to an rvalue value category:
int && ir{::std::move(i)};
my_forward(ir); // my_forward(int&)
Passing type to std::forward will ensure that arguments of rvalue reference types will be moved further as rvalues.
The answer by user7860670 gives you an example for the case where this breaks down. Here is the reason why the explicit template parameter is always needed there.
By looking at the value of the forwarding reference you can no longer reliably determine through overload resolution whether it is safe to move from. When passing an lvalue reference parameter as an argument to a nested function call it will be treated as an lvalue. In particular, it will not bind as an rvalue argument, that would require an explicit std::move again. This curious asymmetry is what breaks implicit forwarding.
The only way to decide whether the argument should be moved onwards is by inspecting its original type. But the called function cannot do so implicitly, which is why we must pass the deduced type explicitly as a template parameter. Only by inspecting that type directly can we determine whether we do or do not want to move for that argument.
I have seen multiple instances of code where function parameter pack is declared using the && notation, as shown below, but I cannot see any advantage to using this notation.
template<typename... Args>
void Function(Args... args)
{
}
template<typename... Args>
void Function(Args&&... args)
{
}
My first thought was that the && form will be used exclusively for r-value objects, but this test proved that wrong:
struct Object
{
// Added bodies so I see what is being called via a step-into
Object() {}
Object(const Object&) {}
Object(Object&&) noexcept {}
Object& operator=(const Object&) { return *this; }
Object& operator=(Object&&) noexcept { return *this; }
};
Object GetObject() { Object o; return o; }
Object obj;
Function(GetObject());
Function(GetObject());
Here, VS 2017 complains that both forms of the function are viable candidates for the call.
Can someone explain what the difference is between these two, and what advantages one may have over the other please?
They are forwarding references in the parameter pack form. As for template parameter deduction, they can match any arguments, but the template parameter will be deduced differently comparing to the ordinary template parameter.
The major advantage of forwarding reference is that the lvalue/rvalue information will be preserved if used with std::forward. Thus they are used to "forward" something.
For example,
void real_foo(A const &a);
void real_foo(A &&a);
template<class... Args>
void foo_proxy_ordinary(Args... args) { real_foo(args...); }
template<class... Args>
void foo_proxy_perfect(Args&&... args) { real_foo(std::forward<Args>(args)...); }
The ordinary version will always call real_foo(A const &) version, because inside foo_proxy, args are always lvalue.
However, the perfect version will select real_foo(A&&) if the arguments passed in are indeed rvalues.
Combining forwarding reference with parameter pack, one can write easily generic proxy functions without performance loss in terms of lvalue/rvalue.
T&& when used in the context of
template<typename T>
void f(T&& t);
is called a forwarding reference sometimes also called a universal reference.
Main advantage of a forwarding reference is that combined with std::forward it enables achieving a so-called perfect forwarding: function template passing its arguments to another function as they are (lvalue as lvalue, rvalue as rvalue).
Now it is possible to create higher-order functions that take other functions as arguments or return them, or superior function-wrappers (e.g., std::make_shared), and do other cool things.
Here is some material that explains it much better and in more detail than I possibly can:
Perfect forwarding and universal references in C++
Rvalue References and Perfect Forwarding in C++0x
Forwarding references proposal
SO: Advantages of using forward
SO: Perfect forwarding - what's it all about?
Can someone explain what the difference is between these two, and what advantages one may have over the other please?
The difference is same for parameter packs as it is for individual parameters. Args declares an "object parameter" (pass by value) and Args&& declares a reference parameter (pass by reference).
Passing by reference allows one to avoid copying the argument when that is unnecessary. It also allows modifying the referred argument if the reference is non-const, which includes the possibility of moving from that object.
Passing by value makes it clear to the caller that the passed object will neither be modified, nor be referred to as a result of calling the function.
My first thought was that the && form will be used exclusively for r-value objects
As your test demonstrates, that is indeed an incorrect assumption. When Args is a deduced type i.e. auto or a template argument, Args&& can indeed be either an l-value reference or an r-value reference. Which one it is depends on what Args is deduced to be. This demonstrates the reference collapsing rules concisely:
typedef int& lref;
typedef int&& rref;
int n;
lref& r1 = n; // type of r1 is int&
lref&& r2 = n; // type of r2 is int& note this case in particular
rref& r3 = n; // type of r3 is int&
rref&& r4 = 1; // type of r4 is int&&
Using such reference allows forwarding i.e. re-binding into a new lvalue reference (when possible) or moving from the object (when possible) or copying (when neither of the previous is possible).
Because of this, Args&& is called a forwarding reference (or a universal reference) when Args is a deduced type.
I would like to have member function that accepts both left and right references to some class and moves it's object. Currently I have two separate functions:
void ThrSafeQueue::push(Obj&& x) {
lock_guard(mutex_);
queue_.push(x);
}
void ThrSafeQueue::push(Obj& x) {
lock_guard(mutex_);
queue_.push(std::move(x));
}
This is what a universal reference is for:
template<typename T>
void ThrSafeQueue::push(T && x) {
lock_guard(mutex_);
queue_.push(std::forward<T>(x));
}
The syntax seems misleading. Here && does not actually designate an rvalue reference, but a universal reference that will bind to either an lvalue or an rvalue, depending on the template call.
std::forward will, depending on whether its template is an rvalue on an lvalue, either degenerate to std::move, or a no-op in a case of an lvalue, exactly what you want.
This example will actually attempt to forward anything, and not just an Obj, to push(). This is probably ok, but if you would like to restrict the invocation of the template to be used only with Objs, the usual tricks involving std::enable_if and std::is_same can be employed, if needed.
The first option is to use template member function (forwarding references), but it can't be virtual and may accept some other types instead of Obj in some cases:
template <typename T>
void ThrSafeQueue::push(T&& x) {
lock_guard(mutex_);
queue_.push(std::move(x));
}
The second option is to leave only function accepting right reference and static_cast<Obj&&> all left references. Instead of static_cast you may use std::move:
ThrSafeQueue t;
t.push(Obj{});
Obj o;
t.push(std::move(o));
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 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.