I have some cases of use of auto:
auto s = expr; //s is always lvalue
auto & s = expr; //s is always lvalue reference? What if expr is rvalue?
auto && s = expr; //s is perfectly forwarded
Are they true? If not, why?
dyp is correct and I would like to elaborate.
First of all, the conclusion is the from dyp:
The type deduced for auto in the declaration of a variable is defined
via the rules of template argument deduction, see [dcl.spec.auto]/6;
with one exception: if the initializer is a braced-init-list, the
deduced type is a std::initializer_list.
I'll explain.
First,
auto s = expr;
This is same as deducing the T from expr,
template<class T>
void f(T s);
f(expr);
The rule for template argument deduction is quite complicated, since you are only concerning with the lvalue and rvalue stuff, let's focus on this.
Template argument deduction is by comparing the template parameter type (call it P, in this case P is T), and the corresponding argument (call it A, in this case, the type of expr).
From 14.8.2.1,
If P is not a reference type:
— If A is an array type, the pointer type produced by the array-to-pointer standard conversion (4.2) is
used in place of A for type deduction; otherwise,
— If A is a function type, the pointer type produced by the function-to-pointer standard conversion (4.3)
is used in place of A for type deduction; otherwise,
— If A is a cv-qualified type, the top level cv-qualifiers of A’s type are ignored for type deduction.
So, if expr is array or function, it will be treated as pointers, if expr has cv-qualification (const etc), they will be ignored.
If P is a cv-qualified type, the top level cv-qualifiers of P’s type are ignored for type deduction.
This actually says:
const auto s = expr;
s is a const variable, but for type deduction for auto purposes, the const will be removed.
Thus, from the above rules, auto will be deduced to the type of expr (after some type conversion stated above).
Note that, when an expression is a reference to T, it will be adjusted to T before prior analysis.
So whatever expr is – rvalue, lvalue, or lvalue/rvalue ref type – the type of auto will always be the type of expr without reference.
auto s1 = 1; //int
int &x = s1;
auto s2 = x; //int
int &&y = 2;
auto s3 = y; //int
Second, let's look at
auto &s = expr;
This will be same as
template<class T>
void f(T &s);
f(expr);
The extra rule from standard is as follows:
If P is a reference type, the type referred to by P is used for type deduction.
So the deduction of auto will be exactly same as without &, but after the auto type is deducted, the & is added to the end of auto.
//auto &s1 = 1; //auto is deducted to int, int &s1 = 1, error!
const auto &s1 = 1; //auto is deducted to int, const int &s1 = 1; ok!
const int &x = s1;
auto &s2 = x; //auto is int, int &s2 = x; ok!
int &&y = 2;
auto &s3 = y; //auto is int, int &s3 = y; ok!
Note that the last y is an lvalue. The rule of C++ is: named rvalue reference is an lvalue.
Lastly:
auto &&s = expr;
This is no doubt same as
template<class T>
void f(T &&s);
f(expr);
One additional rule from standard:
If P is an rvalue reference to a cv-unqualified template parameter and
the argument is an lvalue, the type “lvalue reference to A” is used in
place of A for type deduction.
This actually says that, if expr is an rvalue, the rule will be same as the second case (lvalue case), but if expr is an lvalue, the type of A will be an lvalue reference to A.
Note from previous explained, A is never reference, because the type of an expression is never a reference. But for this special case (auto &&, and A is an lvalue), reference to A must be used, regardless expr itself is a reference type or not.
Example:
auto &&s1 = 1; //auto is deducted to int, int &&s1 = 1, ok!
int x = 1;
auto &&s2 = x; //x is lvalue, so A is int &, auto is deducted to int &, int & &&s2 = x; ok!
int &&y = 2;
auto &&s3 = y; //y is lvalue, auto is int &, int & &&s3 = y; ok!
Since no one gives an answer and I kinda understand now (thanks to #dyp), I would just post answer myself. Point out error if any:
auto s = expr;
If expr is lvalue, prvalue, or any reference, s is always a lvalue and a copy is made.
auto& s = expr;
If expr is lvalue, lvalue reference or rvalue reference, s is lvalue reference.
If expr is prvalue, it is an error and not legal.
auto&& s = expr;
s is perfectly forwarded (rvalue would become rvalue reference and collapse).
Related
Consider the following example:
template<typename T>
void f(T&) {};
int main(void){
const int i = 0;
const int& r = i;
f(r);
}
Let's name the template parameter type as P and the template argument type as A. Then, P and A will be:
P = T&, A = const int;
Since P is a reference type, [temp.deduct.call]/2 is not applied, but I think [temp.deduct.call]/3 is, which says:
If P is a cv-qualified type, the top-level cv-qualifiers of P's type
are ignored for type deduction. If P is a reference type, the type
referred to by P is used for type deduction.
After applying the above quote, P and A will be:
P = T, A = const int; // T = const int;
I got stuck when I applied the same concept to this example:
template<typename T>
void g(const T&) {};
int main(void)
{
int i = 0;
g(i);
}
The types of the template parameter P and its corresponding template argument A are:
P = const T&, A = int;
After applying the above quote, P and A will be:
P = const T, A = int; // Is type deduction failed here?
Per my knowledge, T cannot be deduced because argument type A requires a qualification conversion to match the parameter type P. Am I correct to say that? if no, what's the main reason that causes deducing const T from int fails? Just if possible, provide a rule from the C++ standard for that. This is my first question.
But when I tested that code in g++11.2.0, the program compiles fine, and the instantiated function is g<int>(const int&).
So it's seemed that the T has been deduced as int; why? this is my second question.
Scott Meyers' Effective Modern C++ explains exactly this case in the Item 1: Understanding template type deduction, Case 1: ParamType is a Reference or Pointer, but not a Universal Reference.
Considering a code structure as below:
template <typename T>
void f(ParamType param);
f(expr);
Where we want to deduce T and ParamType from expr.
And given the case that ParamType is a reference (not a universal reference; it's const T& in your example).
The deduction would work as:
If expr's type is a reference, ignore the reference part.
Then pattern-match expr's type against ParamType to determine T.
For the particular case of:
template<typename T>
void g(const T&);
int i = 0;
g(i);
i's type (expr's type) is int.
Pattern matching int against const T& (ParamType) determines T's type as int.
I can try reverse engineering my answer with references to cppreference and The Standard:
cppreference: from Eljay's comment link:
The comment 3) If P is a reference type, the referenced type is used for deduction. tells you to adjust P's type to const T.
Then you have a detailed example of how deduction works under 1) If P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than the transformed A:.
template<typename T>
void f(const T& t);
bool a = false;
f(a); // P = const T&, adjusted to const T, A = bool:
// deduced T = bool, deduced A = const bool
// deduced A is more cv-qualified than A
The Standard: from temp.deduct.call/3: If P is a cv-qualified type, the top-level cv-qualifiers of P's type are ignored for type deduction. If P is a reference type, the type referred to by P is used for type deduction.. Example 3 below that text contains almost exactly your example, apart from the fact that the argument is const int.
template<class T> int f(const T&);
const int i = 0;
int n2 = f(i); // calls f<int>(const int&)
P's type is const T&.
Since P is a reference type, the type referred to by P, i.e. const T, is used for type deduction.
In your example, A's type is int. Deducing T as int would match P's adjusted type as const int.
Per my knowledge, T cannot be deduced because argument type A requires a qualification conversion to match the parameter type P. Am I correct to say that? This is my first question.
No, your assumption/understanding that a conversion from int to const int requires a qualification conversion is incorrect. An example of a qualification conversion would be something like from int* to const int*.
So it's seemed that the T has been deduced as int; why? this is my second question.
This can be understood from temp.deduct#call-4 which states:
In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above).
However, there are three cases that allow a difference:
4.1) If the original P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than the transformed A.
4.2) The transformed A can be another pointer or pointer-to-member type that can be converted to the deduced A via a function pointer conversion and/or qualification conversion.
(emphasis mine)
Now we can apply this to your example:
template<typename T>
void g(const T&) {};
int main(void)
{
int i = 0;
g(i);
}
In above, P is const T& so it is adjusted to const T. And since the passed argument is A = int, the deduced T will be int while the deduced A will be const int.
The same explanation can be found here:
template<typename T>
void f(const T& t);
bool a = false;
f(a); // P = const T&, adjusted to const T, A = bool:
// deduced T = bool, deduced A = const bool
// deduced A is more cv-qualified than A
The following code snippet is excerpted from cppref:
std::tuple<int, int&> f();
auto [x, y] = f();
// decltype(x) is int
// decltype(y) is int&
const auto [z, w] = f();
// decltype(z) is const int
// decltype(w) is int&
My question is at the last line:
Why is decltype(w) int& rather than const int&?
Jarod42 answered the question the question in the comments, let me just cite the relevant part of the standard here, from [dcl.struct.bind]¹:
Given the type Ti designated by std::tuple_element::type, variables are introduced with unique names ri of type “reference to Ti” initialized with the initializer ([dcl.init.ref]), where the reference is an lvalue reference if the initializer is an lvalue and an rvalue reference otherwise.
Each vi is the name of an lvalue of type Ti that refers to the object bound to ri; the referenced type is Ti.
Hence in const auto [z, w] = f();, you have const T1 with T1 being int and const T2 with T2 being int&. As const modifies what's on its left, this becomes int& const and results in int&.
Note that int& const becoming int& is only possible in template argument substitution, i.e., this won't compile:
int n = 42;
int& const doesntWork = n; // Error: 'const' qualifiers cannot be applied to 'int&'
but this does:
template <class T> void f(const T t)
{
++t;
}
int n = 42;
f<int&>(n);
where the identical contraction from int& const to int& as above takes place.
¹ Thanks to #cpplearner for pointing me to the exact paragraph here.
It would be the reference itself rather than the referenced value that would be const. As references aren't modifiable anyway there is no such thing as a constant reference.
Why auto&& is not rvalue reference?
Widget&& var1 = Widget(); // rvalue reference
auto&& var2 = var1; //var2 not rvalue reference
below are rvalue reference example
void f(Widget&& param); // rvalue reference
Widget&& var1 = Widget(); // rvalue reference
Why var2 is not rvalue reference but f and var2 are rvalue references?
auto&& is a declaration's equivalent of forwarding references (with identical deduction rules). As such, it will be deduced to an lvalue reference when the initializer is an lvalue. However, var is an lvalue (as it is the name of a variable), hence var2 is an lvalue reference.
Once the type of the initializer has been determined, the compiler determines the type that will replace the keyword auto using the rules for template argument deduction from a function call (see template argument deduction#Other contexts for details). The keyword auto may be accompanied by modifiers, such as const or &, which will participate in the type deduction.
For example, given
const auto& i = expr;
The type of i is exactly the type of the argument u in an imaginary
template template<class U>
void f(const U& u)
If the function call f(expr) was compiled.
In general , it can be think as below .
template template<class U>
void f(paramtype u)
Therefore, auto&& may be deduced either as an lvalue reference or rvalue reference according to the initializer.
In your case , imaginary template would look like
template template<class U>
void f(U&& var2){}
f(var1)
Here ,var1 is named rvalue which is being treated as lvalue, so var2 will be deduced as lvalue .
Consider the following examples:
auto&& var2 = widget() ; //var2 is rvalue reference here .
int x=10;
const int cx=10;
auto&& uref1 = x; // x is int and lvalue, so uref1's type is int&
auto&& uref2 = cx; // cx is const int and lvalue, so uref2's type is const int&
auto&& uref3 = 27; // 27 is int and rvalue, so uref3's type is int&&
I am reading Bjarne's Rvalue Reference Quick Look and came to the following example:
template <class T, class A1>
std::shared_ptr<T>
factory(A1& a1)
{
return std::shared_ptr<T>(new T(a1));
}
This is much better. If a const-qualified type is passed to the
factory, the const will be deduced into the template parameter (A1 for
example) and then properly forwarded to T's constructor.
I do not understand how ::factory() can accept a const reference. Bjarne just states that the const will be deduced into the template parameter. What exactly does this mean?
If you pass in an lvalue of type const X, then A1 will be deduced as const X, and you will get a function that looks like
std::shared_ptr<T> factory(const X& a1) { ... }
template type deduction normally happen in three cases:
Parameter type is a reference or pointer, but not a universal
reference Parameter type is a universal reference Parameter type
is a neither reference nor pointer
your case falls into non-universal reference parameters deduction. The rules will be:
if expression is a reference, ignore that
pattern-match expression's type against parameter type to determine type T
For example:
template<typename T>
void factory(T& param); // param is a reference
int x = 42; // int
const int cx = x; // copy of int
const int& rx = x; // ref to const view of int
factory(x); // 1, T = int, param's type = int&
factory(cx); // 2, T = const int, param's type = const int&
factory(rx); // 3, T = const int, param's type = const int&
Now you can see when you pass in const X it matches case 2 and if you pass in const X& it matches case 3.
Note: std::shared_ptr is the noise in this your sample, I remove it to demonstrate type deduction.
It's generally a bad idea to overload a function template taking a "T&&" parameter, because that can bind to anything, but let's suppose we do it anyway:
template<typename T>
void func(const T& param)
{
std::cout << "const T&\n";
}
template<typename T>
void func(T&& param)
{
std::cout << "T&&\n";
}
My understanding has been that the const T& overload will get called for arguments that are const lvalues, and the T&& overload will get called for all other argument types. But consider what happens when we call func with arrays of const and non-const contents:
int main()
{
int array[5] = {};
const int constArray[5] = {};
func(array); // calls T&& overload
func(constArray); // calls const T& overload
}
VC10, VC11, and gcc 4.7 agree on the results shown. My question is why the second call invokes the const T& overload. The simple answer is that constArray has a const in it, but I think that's too simple. The type T that is deduced (regardless of the template selected) is "array of 5 const ints" so the type of param in the const T& overload would be "reference to const array of 5 const ints". But the array named constArray is not itself declared const. So why doesn't the call to func(constArray) invoke the T&& overload, thus yielding a type for param of "reference to array of 5 const ints"?
This question is motivated by the discussion associated with the question at c++ template function argument deduce and function resolution, but I think that thread got sidetracked on other issues and did not clarify the question I'm now asking here.
In function parameter lists (as well as everywhere else), a cv qualification on an array type is shuffled right to qualify the array element type. For example, with T = int [5], const T & is converted to int const (&) [5].
3.9.3 CV-qualifiers [basic.type.qualifier]
2 - [...] Any cv-qualifiers applied to an array type affect the array element type, not the array type (8.3.4).
So the call to func with an argument of type int const [5] is deduced as a call to either of:
void func<int [5]>(int const (&) [5])
void func<int const (&) [5]>(int const (& &&) [5])
// where the above collapses to
// 'void func<int const (&) [5]>(int const (&) [5])'
Both overloads are viable, but the former is preferred:
Let T1 be the const T & template and T2 be the T && template; that is, their parameter types are T1 := const T & and T2 := T &&. Then the transformed argument types (14.5.6.2:3) can be written A1 := const C &, A2 := D && for synthesized types C, D.
Now, we attempt to order T1 against T2 (14.8.2.4:2), first using A1 as the argument template and P2 as the parameter template. We remove references (14.8.2.4:5) giving A1 -> const C and T2 -> T, then remove cv-qualification (14.8.2.4:7) giving A1 -> C and T2 -> T. The template T can be deduced to C (14.8.2.4:8) so A1 is at least as specialised as P2; conversely, A2 -> D -> D, P1 -> const T -> T, and T can be deduced to D, so A2 is at least as specialised as P1.
This would usually imply that neither is more specialised than the other; however, because the P and A types are reference types 14.8.2.4:9 applies, and since A1 is an lvalue reference and P2 is not, T1 is considered more specialized than T2. (A tie between reference types can also be broken by cv-qualification under the same clause.)
You're confusing rvalue references (like int&&) and universal references (which are made from template parameters, as in template <typename T> ... T&&).
Rvalue references indeed don't bind to lvalues. But universal references bind to anything. The question is just who's a better match.
The type you have is int const [5]. Now let's see:
against T const &: Matches with T = int[5].
against T &&: Matches with T = int const (&)[5].
The former is a better match, in the following sense: Both templates produce identical overloads. But T = int[5] is more specialised than T = int const (&)[5]. You can see this because T = int const (&)[5] can be realised as T = U const & with U = int[5].
Note that to bind an lvalue to a universal reference, the type itself has to be deduced as a reference type.
(Obviously array doesn't match const T &, because it is not const. It can only match T&&, deducing T = int (&)[5]).