Argument type deduction, references and rvalues - c++

Consider the situation where a function template needs to forward an argument while keeping it's lvalue-ness in case it's a non-const lvalue, but is itself agnostic to what the argument actually is, as in:
template <typename T>
void target(T&) {
cout << "non-const lvalue";
}
template <typename T>
void target(const T&) {
cout << "const lvalue or rvalue";
}
template <typename T>
void forward(T& x) {
target(x);
}
When x is an rvalue, instead of T being deduced to a constant type, it gives an error:
int x = 0;
const int y = 0;
forward(x); // T = int
forward(y); // T = const int
forward(0); // Hopefully, T = const int, but actually an error
forward<const int>(0); // Works, T = const int
It seems that for forward to handle rvalues (without calling for explicit template arguments) there needs to be an forward(const T&) overload, even though it's body would be an exact duplicate.
Is there any way to avoid this duplication?

This is a known problem and the purpose of rvalue references in C++0x. The problem has no generic solution in C++03.
There is some archaic historical reason why this occurs that is very pointless. I remember asking once and the answer depressed me greatly.

In general, this nasty problem with templates ought to require duplication because the semantics where a variable is const or not, or a reference or not, are quite distinct.
The C++11 solution to this is "decltype" but it is a bad idea because all it does is compound and already broken type system.
No matter what the Standard or the Committee says, "const int" is not and will never be a type. Nor will "int&" ever be a type. Therefore a type parameter in a template should never be allowed to bind to such non-types, and thankfully for deduction this is the case. Unfortunately you can still explicitly force this unprincipled substitution.
There are some idiotic rules which try to "fix" this problem, such as "const const int" reducing to "const int", I'm not even sure what happens if you get "int & &": remember even the Standard doesn't count "int&" as a type, there is a "int lvalue" type but that's distinct:
int x; // type is lvalue int
int &y = x; // type is lvalue int
The right solution to this problem is actually quite simple: everything is a mutable object. Throw out "const" (it isn't that useful) and throw away references, lvalues and rvalues. It's quite clear that all class types are addressable, rvalue or not (the "this" pointer is the address). There was a vain attempt by the committee to prohibit assigning to and addressing rvalues.. the addressing case works but is easily escaped with a trivial cast. The assignment case doesn't work at all (because assignment is a member function and rvalues are non-const, you can always assign to a class typed rvalue).
Anyhow the template meta-programming crowd have "decltype" and with that you can find the encoding of a declaration including any "const" and "&" bits and then you can decompose that encoding using various library operators. This couldn't be done before because that information is not actually type information ("ref" is actually storage allocation information).

When x is an rvalue
But x is never an rvalue, because names are lvalues.
Is there any way to avoid this duplication?
There is a way in C++0x:
#include <utility>
template <typename T>
void forward(T&& x) // note the two ampersands
{
target(std::forward<T>(x));
}
Thanks to reference collapsing rules, the expression std::forward<T>(x) is of the same value-category as the argument to your own forward function.

Assuming there are k arguments, as I understand it the only "solution" in C++03 is to manually write out 2^k forwarding functions taking every possible combination of & and const& parameters. For illustration, imagine that target() actually took 2 parameters. You would then need:
template <typename T>
void forward2(T& x, T& y) {
target(x, y);
}
template <typename T>
void forward2(T& x, T const& y) {
target(x, y);
}
template <typename T>
void forward2(T const& x, T& y) {
target(x, y);
}
template <typename T>
void forward2(T const& x, T const& y) {
target(x, y);
}
Obviously this gets very unwieldy for large k, hence rvalue references in C++0x as other answers have mentioned.

Related

Why does a T&& parameter preserves the constness and lvalue/rvalue property of the argument while a T& doesn't

I've just read this in C++ Primer :
A function parameter that is an rvalue reference to a template type
parameter (i.e., T&&) preserves the constness and lvalue/rvalue
property of its corresponding argument.
But I don't understand why T&& parameters have this feature while T&'s haven't.
What's the C++ logic behind this ?
Because if T is a function template argument, then T& is a non-constant reference to T while T&& is called a forwarding reference if used as type for a function argument. Other T&&, e.g. in template<typename T> void foo(std::vector<T&&> x) is really an r-value reference that cannot deduce const (not that const r-value references are very useful).
Since we want to automatically differentiate between the following cases:
const int x = 5; foo(x);
int x = 5; foo(std::move(x));
int x = 5; foo(x);
foo(int{5});
while retaining a single template<typename T> foo(T&& x) definition, forwarding references were given the ability to deduce const.
As to why T& cannot deduce const, it likely never was the intent of this feature. It really meant to serve as a non-constant reference to a generic type. It would make x.non_const_member() fail to compile sometimes.
Yes, that happens for T&& too but the intent here is exactly about forwarding the type as it was passed to us to somewhere else, not necessarily modifying the object ourselves.

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.

Why type deduced for T is const int in void func(T const &t)

I am reading about type deduction in templates, and here is a question that bothers me
template<typename T>
void funt(T const &t);
int x = 10;
func(x);
T will be deduced to be const int, and the type of t will be int const &
I understand the reasons for why t has to be int const &, so that xpassed to the function would remain the same, because function accepts const T&.
But I don't see the reason why T has to be also const. Seems to me that deducing T to be int would not break anything within this piece of code?
Like in another example:
template<typename T>
void funt(T const &t);
int const x = 10;
func(x);
Here T is deduced to just int, and const of x is omitted
Or am I missing something here?
The type deduction rules are not very straightforward. But as a general rule of thumb, there are 3 main type-deduction cases:
f(/*const volatile*/ T& arg) - In this instance, the compiler performs something similar to "pattern matching" on the argument to deduce the type T. In your second case, it perfectly matches the type of your argument, i.e. int const, with T const, hence T is deduced as int. In case the argument lacks CV-qualifiers, like in your first example, then the compiler does not consider them in the pattern matching. So, when you pass e.g. an int x to f(const T& param), then const is being discarded during the pattern matching and T is deduced as int, hence f being instantiated to f(const int&).
f(/*const volatile*/ T arg) - The cv-ness (const/volatile) and the references-ness of the argument is ignored. So if you have something like const int& x = otherx; and pass x to template<class T> void f(T param), then T is deduced as int.
f(T&& arg) - T is deduced as either a lvalue reference (like int&) or a rvalue (like int), depending whether arg is a lvalue or a rvalue, respectively. This case is a bit more complicated, and I advise to read more about forwarding references and reference collapsing rules to understand what's going on.
Scott Meyers discusses template type deduction in Chapter 1 of Effective Modern C++ book in great detail. That chapter is actually free online, see https://www.safaribooksonline.com/library/view/effective-modern-c/9781491908419/ch01.html.

Why is T const&& not a forwarding reference?

In the context of a template, the following "reference collapsing" rules are applied:
template <typename T>
void foo(T && t)
{
//T& & -> T&
//T& && -> T&
//T&& & -> T&
//T&& && -> T&&
}
Why does the language prohibit "universal references" from having const qualifiers?
template <typename T>
void foo(T const && t)
It would seem to make sense if the type had resolved to a reference (3 out of the 4 cases).
I'm sure this idea is incompatible with some other design aspect of the language, but I can't quite see the full picture.
Originally the rvalue reference proposal said that the transformation happens if P is "an rvalue reference type". However, a defect report later noticed
Additionally, consider this case:
template <class T> void f(const T&&);
...
int i;
f(i);
If we deduce T as int& in this case then f(i) calls f<int&>(int&), which seems counterintuitive. We prefer that f<int>(const int&&) be called. Therefore, we would like the wording clarified that the A& deduction rule in 14.8.2.1 [temp.deduct.call] paragraph 3 applies only to the form T&& and not to cv T&& as the note currently implies.
There appears to have been a time period where const T &&, with T being U&, was transformed to const U&. That was changed to be consistent with another rule that says that const T, where T is U& would stay U& (cv-qualifiers on references are ignored). So, when you would deduce T in above example to int&, the function parameter would stay int&, not const int&.
In the defect report, the reporter states "We prefer that f<int>(const int&&) be called", however provides no reason in the defect report. I can imagine that the reason was that it seemed too intricate to fix this without introducing inconsistency with other rules, however.
We should also keep in mind that the defect report was made at a time where rvalue references could still bind to lvalues - i.e const int&& could bind to an int lvalue. This was prohibited only later on, when a paper by Dave & Doug, "A Safety Problem with RValue References", appeared. So, it seems to me that a deduction that works (at that time) was worth more than a deduction that simply was counter intuitive and dropped qualifiers.
This does already happen for references; if your T is a U const &, then T && will collapse to U const &. The term "universal reference" really does mean universal reference: you don't need to specify const in there to get a constant reference.
If you want to have a truly universal reference mechanism, you need your T && to be able to become all kinds of references, will all kinds of constness. And, T && does exactly that. It collapses to all four cases: both l- and r-value references, and both const and non-const.
Explained another way, the constness is an attribute of the type, not the reference, i.e. when you say T const &, you are actually talking about a U &, where U is T const. The same is true for && (although an r-value reference to a const is less useful).
This means that if you want your universal reference to collapse to a U const &, just pass it something that is of the type you want: a U const &, and it will collapse to exactly that.
To answer you question more directly: the language does not "prohibit" the use of const in the declaration of a universal reference, per sé. It is saying that if you change the mechanism for declaring a universal reference even a little bit - even by inserting a lowly const between the T and the && - then you won't have a (literally) "universal" reference anymore, because it just won't accept anything and everything.
Why do you think the language does not allow const r-value references?
In the following code, what will be printed?
#include <iostream>
struct Foo
{
void bar() const &
{
std::cout << "&\n";
}
void bar() const &&
{
std::cout << "&&\n";
}
};
const Foo make() {
return Foo{};
}
int main()
{
make().bar();
}
answer:
&&
why? Because make() returns a const object and in this context it's a temporary. Therefore r-value reference to const.
Template argument deduction has a special case for "rvalue reference to cv-unqualified template parameters". It is this very special case that forwarding/universal references rely on. See section "Deduction from a function call" in the linked article for details.
Note that before template argument deduction, all top-level cv-qualifiers are removed; however, references never have top-level cv-qualifiers and above rule does not apply, so the special rule also does not apply. (In contrast to pointers, there is no "const reference", only "reference to const")

C++ Templates can't deduce Reference Types without using '&'

Here's a very simple example:
#include <iostream>
template<typename T>
void DoubleMe(T x) {
x += x;
}
int main()
{
int a = 10;
DoubleMe(a);
std::cout << a; //displays 10 not 20!
}
In cases like this, am I forced to use 'T&' in the function argument instead? Cuz I have read in some tutorials that templates can correctly deduce the appropriate data type, including T*, T[ ], or T& by just defining a simple 'T' before the variable. Help?
You can indeed correctly deduce reference types with plain T. Unfortunately, that does not mean what you think it means.
Given
template <typename T> struct S { };
template <typename T> void f(S<T>) { }
int main() { f(S<int&>{}); }
the type argument for f is correctly deduced as int&.
The problem is that in your case, deducing T as int produces a perfectly valid function already. Perhaps a slight oversimplification, but type deduction produces the simplest T that makes the call work. In your case, T = int makes the call work. Not the way you want it to work, but the compiler can't know that. T = int & would also make the call work, but it's not the simplest T that makes it work.
Maybe this will help you to see it the way the compiler does (apologies to any language lawyers if I am oversimplifying):
In this example, the compiler must infer the type of T to be the type that makes the function declaration legal with the least amount of deduction. Since a T can usually be copy-constructed directly from a const T& (which implies that it can also be copy-constructed from a T&), your function will take a copy.
template<class T>
void foo(T value);
In this example T must be the type of the object thing ref refers to - and since references cannot refer to references, T must be a (possibly const, possibly volatile) type.
template<class T>
void foo(T& ref);
In this example, ref must be referring to a const (possibly volatile) object of type T:
template<class T>
void foo(const T& ref);
In this example, ref may either be a reference or an r-value reference. It's known as a universal reference. You're actually writing two functions in one and it's often the most efficient way to handle the case where you are taking ownership of ref.
template<class T>
void foo(T&& ref);
// called like this:
foo(Bar()); // means ref will be a Bar&&
// called like this:
Bar b;
foo(b); // means ref will be a Bar&
// called like this:
const Bar b;
foo(b); // means ref will be a const Bar&
In summary:
void foo(T value) means I will make a copy of whatever you give me.
void foo(T& value) means I wont make a copy but I may modify your value. You may not give me a temporary.
void foo(const T& value) means I wont make a copy and I cannot modify your copy. You may give me a temporary.
void foo(const T&& value) means I may steal or modify the contents of your value, even if it's a temporary.
Yes, to get the effect you want here, you must add the ampersand.
As you write, templates can correctly deduce data types. However, they cann deduce intent. In this case, the type you pass it is an integer, and it correctly instantiates an integer function that internally doubles the argument passed to it by value. The fact that you meant the function to have a side effect, is not something the compiler can guess.
If you want the function to pass the argument by value, you need to return the value with the same argument type.
T DoubleMe(T x) {
return x + x;
}