How to accept both right and left references in C++? - c++

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));

Related

Is it possible to omit template parameter in `std::forward`?

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.

How do correctly use a callable passed through forwarding reference?

I'm used to pass lambda functions (and other callables) to template functions -- and use them -- as follows
template <typename F>
auto foo (F && f)
{
// ...
auto x = std::forward<F>(f)(/* some arguments */);
// ...
}
I mean: I'm used to pass them through a forwarding reference and call them passing through std::forward.
Another Stack Overflow user argue (see comments to this answer) that this, calling the functional two or more time, it's dangerous because it's semantically invalid and potentially dangerous (and maybe also Undefined Behavior) when the function is called with a r-value reference.
I've partially misunderstand what he means (my fault) but the remaining doubt is if the following bar() function (with an indubitable multiple std::forward over the same object) it's correct code or it's (maybe only potentially) dangerous.
template <typename F>
auto bar (F && f)
{
using A = typename decltype(std::function{std::forward<F>(f)})::result_type;
std::vector<A> vect;
for ( auto i { 0u }; i < 10u ; ++i )
vect.push_back(std::forward<F>(f)());
return vect;
}
Forward is just a conditional move.
Therefore, to forward the same thing multiple times is, generally speaking, as dangerous as moving from it multiple times.
Unevaluated forwards don't move anything, so those don't count.
Routing through std::function adds a wrinkle: that deduction only works on function pointers and on function objects with a single function call operator that is not && qualified. For these, rvalue and lvalue invocation are always equivalent if both compiles.
I'd say the general rule applies in this case. You're not supposed to do anything with a variable after it was moved/forwarded from, except maybe assigning to it.
Thus...
How do correctly use a callable passed through forwarding reference?
Only forward if you're sure it won't be called again (i.e. on last call, if at all).
If it's never called more than once, there is no reason to not forward.
As for why your snippet could be dangerous, consider following functor:
template <typename T>
struct foo
{
T value;
const T &operator()() const & {return value;}
T &&operator()() && {return std::move(value);}
};
As an optimization, operator() when called on an rvalue allows caller to move from value.
Now, your template wouldn't compile if given this functor (because, as T.C. said, std::function wouldn't be able to determine return type in this case).
But if we changed it a bit:
template <typename A, typename F>
auto bar (F && f)
{
std::vector<A> vect;
for ( auto i { 0u }; i < 10u ; ++i )
vect.push_back(std::forward<F>(f)());
return vect;
}
then it would break spectacularly when given this functor.
If you're either going to just forward the callable to another place or simply call the callable exactly once, I would argue that using std::forward is the correct thing to do in general. As explained here, this will sort of preserve the value category of the callable and allow the "correct" version of a potentially overloaded function call operator to be called.
The problem in the original thread was that the callable was being called in a loop, thus potentially invoked more than once. The concrete example from the other thread was
template <typename F>
auto map(F&& f) const
{
using output_element_type = decltype(f(std::declval<T>()));
auto sequence = std::make_unique<Sequence<output_element_type>>();
for (const T& element : *this)
sequence->push(f(element));
return sequence;
}
Here, I believe that calling std::forward<F>(f)(element) instead of f(element), i.e.,
template <typename F>
auto map(F&& f) const
{
using output_element_type = decltype(std::forward<F>(f)(std::declval<T>()));
auto sequence = std::make_unique<Sequence<output_element_type>>();
for (const T& element : *this)
sequence->push(std::forward<F>(f)(element));
return sequence;
}
would be potentially problematic. As far as my understanding goes, the defining characteristic of an rvalue is that it cannot explicitly be referred to. In particular, there is naturally no way for the same prvalue to be used in an expression more than once (at least I can't think of one). Furthermore, as far as my understanding goes, if you're using std::move or std::forward or whatever other way to obtain an xvalue, even on the same original object, the result will be a new xvalue every time. Thus, there also cannot possibly be a way to refer to the same xvalue more than once. Since the same rvalue cannot be used more than once, I would argue (see also comments underneath this answer) that it would generally be a valid thing for an overloaded function call operator to do something that can only be done once in case the call happens on an rvalue, for example:
class MyFancyCallable
{
public:
void operator () & { /* do some stuff */ }
void operator () && { /* do some stuff in a special way that can only be done once */ }
};
The implementation of MyFancyCallable may assume that a call that would pick the &&-qualified version cannot possibly happen more than once (on the given object). Thus, I would consider forwarding the same callable into more than one call to be semantically broken.
Of course, technically, there is no universal definition of what it actually means to forward or move an object. In the end, it's really up to the implementation of the particular types involved to assign meaning there. Thus, you may simply specify as part of your interface that potential callables passed to your algorithm must be able to deal with being called multiple times on an rvalue that refers to the same object. However, doing so pretty much goes against all the conventions for how the rvalue reference mechanism is generally used in C++, and I don't really see what there possibly would be to be gained from doing this…

'&&' in function parameter pack

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.

Function forwarding argument and simply doing nothing

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.
}

Usage of std::forward vs std::move

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.