`auto` specifier type deduction for references
The simplest way to think about it is comparing it to template argument deduction.
Given:
template<typename T>
void deduce(T) { }
If you call:
deduce(px);
then the template argument T
will be deduced as int*
and if you call
deduce(rx);
then T
will be deduced as int
, not int&
You get the same types deduced when using auto
.
One could draw an analogy from pointer types expecting that the deduced type of
arx
isint&
You'd have to have a fairly confused model of the C++ language to make that analogy. Just because they are declared in syntactically similar ways, as Type@
with a type and a modifier doesn't make them work the same way. A pointer is a value, an object, and it can be copied and have its value altered by assignment. A reference is not an object, it's a reference to some object. A reference can't be copied (copying it copies the referent) or altered (assigning to it alters the referent). A function that returns a pointer returns an object by value (the object in question being a pointer object), but a function that returns a reference (like your GetBigClass()
) returns an object by reference. They're completely different semantics, trying to draw analogies between pointers and references is doomed to failure.
auto return type not deducing reference
Given auto
, which is declared as a non-reference, so we're dealing with pass-by-value case. And auto
follows the rules of template argument deduction; the reference part of int&
will be ignored, then the deduced type is int
.
You can use decltype(auto)
(since C++14) instead.
type is
decltype(e)
, wheree
is the initializer.
template <typename T>
decltype(auto) foo(T&& o) { // no sample code is complete without a foo
return o.get();
}
The return type is deduced as decltype(o.get())
, and according to the rule of decltype
,
if the value category of expression is lvalue, then decltype yields
T&
;
c.get()
returns int&
, which is an lvalue, then we get the return type int&
insteand of int
.
BTW: Note that if o
is still passed by-value, the returned reference would be dangled.
How does auto deduce type?
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 astd::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 ofP
’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 byP
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 ofA
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!
What are the type deduction rules for auto*?
If you know template type deduction you will know almost all there is to auto
type deduction. Because auto type deduction works like template type deduction.
When a variable is declared using auto
, then auto
acts as T
in a template, and the type specifier acts as the parameter type:
const auto i = 20;
Would translate to:
template<typename T>
void func(const T param) { ... }
// ^^^^^^^
And with reference:
const auto& j = i;
Translates to:
template<typename T>
void func(const T& param)
// ^^^^^^^^
With pointers, it's the same:
auto* v1 = &x;
Becomes
template<typename T>
void func(T* param)
Since x
is an int
, then auto* == int*
.
And auto* v2 = px;
is also int*
Now, the third one you have:
auto* v3 = &px;
Becomes int**
since you're taking the address of the pointer.
template<typename T>
void func(T** param)
// ^^^
A handy way to see the type of auto is to use what others have mentioned, the typeid()
function.
But I like to use <boost/type_index.hpp>
to show the type correctly:
#include <iostream>
#include <boost/type_index.hpp>
using namespace std;
using namespace boost::typeindex;
int main()
{
int x = 64;
int* px = &x;
auto* v1 = &x;
auto* v2 = px;
auto* v3 = &px;
cout << type_id_with_cvr<decltype(v1)>().pretty_name() << '\n';
cout << type_id_with_cvr<decltype(v2)>().pretty_name() << '\n';
cout << type_id_with_cvr<decltype(v3)>().pretty_name() << '\n';
}
Which outputs:
int*
int*
int**
There is one important difference between auto type deduction and template type deduction, namely std::initializer_list<>
Consider these examples:
auto i = 1; // int
auto j(1); // int
auto k = { 1 }// std::initializer_list<int> !
auto l { 1 } // std::initializer_list<int> !
As you see, using brace initializer with auto can be trouble.
You can however manually write the type before the braces to ensure that the type is correct but I don't see the point in that:
auto i = int{ 1 }; // type is int
There are new auto rules that have been implemented already in Clang 3.8 that makes it possible to use direct-list-initialization with auto (upcoming standard)
decltype(auto) type deduction: return x vs. return (x)
The FAQ is correct, intuition notwithstanding
(@lubgr pointed out a relevant answer to another question)
The language specification says:
If the argument is either the unparenthesised name of an object/function ... then the decltype specifies the declared type of the entity specified by this expression.
If the argument is any other expression of type T, then ...
b) if the value category of expression is lvalue, then the decltype specifies T&
...
Note that if the name of an object is parenthesised, it becomes an lvalue expression, thus decltype(arg) and decltype((arg)) are often different types.
So, the non-parenthesized case is an exceptional/special case, apparently introduced to facilitate returning references with decltype(auto); without the special-casing, the rule @StoryTeller quotes is in effect:
If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis.
and this would have make it difficult to return references.
Definitely ranks up there with other magick definitions such as the destruction order of tuples or empty strings having 0 at index 0, temporary lifetime extension with references and other similar wonders...
C++11: Standard ref for action of `auto` on const and reference types
It is in §7.1.6.4 auto specifier. In your examples of function return types, the rules of template argument deduction apply.
Paraquoting the relevant example from the standard:
const auto &i = expr;
The type of
i
is the deduced type of the parameter X in the callf(expr)
of the following invented function template:
template <class AUTO> void f(const AUTO& X);
So in your examples, the types of all your variables x11
to x23
are deduced as T
.
Related Topics
Does Caffe Need Data to Be Shuffled
Setting Roi with Mouse from a Rectangle on a Video
2D Diamond (Isometric) Map Editor - Textures Extended Infinitely
How to Solve -------Undefined Reference to '_Chkstk_Ms'-------On Mingw
How to Install Feature Based on the Property Set in Custom Action
Using Boost::Iostreams::Mapped_File_Source with Std::Multimap
Paint a Rect on Qglwidget at Specifit Times
Undefined Reference to 'Stdscr' While Using Ncurses
Various Questions About Rsa Encryption
C++ Add Months to Chrono::System_Clock::Time_Point
Srand(Time(Null)) Generating Similar Results
C++ Warning: Address of Local Variable
New() Without Delete() Is Undefined Behavior or Merely Memory Leak
Std::This_Thread::Sleep_For Sleeps for Too Long