Auto Keyword Behavior with References

auto keyword behavior with references

When auto is deduced, it is not deduced to the reference. It always deduces to the value. If you want to have a reference to returned value, you can have auto&, const auto& or auto&&.

And yes, it is by design. Any other behavior would actually be quite surprising. What is even worse, it is easy to use a reference when you want. However, imagine that auto would be actually deduced to the reference. How would you (syntactically) make it a value?

auto keyword strange behavior in C++11

The auto storage class specifier is not "useless and deprecated in C++11," it has been removed entirely. The auto keyword is no longer a storage class specifier and cannot be used as one.

In C++11, auto is a simple type specifier.

C++11 auto: what if it gets a constant reference?

Read this article: Appearing and Disappearing consts in C++


Type deduction for auto variables in C++0x is essentially the same as
for template parameters. (As far as I know, the only difference
between the two is that the type of auto variables may be deduced from
initializer lists, while the types of template parameters may not be.)
Each of the following declarations therefore declare variables of type
int (never const int):

auto a1 = i;
auto a2 = ci;
auto a3 = *pci;
auto a4 = pcs->i;

During type deduction for template parameters and auto variables, only
top-level consts are removed. Given a function template taking a
pointer or reference parameter, the constness of whatever is pointed
or referred to is retained:

template<typename T>
void f(T& p);

int i;
const int ci = 0;
const int *pci = &i;

f(i); // as before, calls f<int>, i.e., T is int
f(ci); // now calls f<const int>, i.e., T is const int
f(*pci); // also calls f<const int>, i.e., T is const int

This behavior is old news, applying as it does to both C++98 and
C++03. The corresponding behavior for auto variables is, of course,
new to C++0x:

auto& a1 = i;       // a1 is of type int&
auto& a2 = ci; // a2 is of type const int&
auto& a3 = *pci; // a3 is also of type const int&
auto& a4 = pcs->i; // a4 is of type const int&, too

Since you can retain the cv-qualifier if the type is a reference or pointer, you can do:

auto& my_foo2 = GetFoo();

Instead of having to specify it as const (same goes for volatile).

Edit: As for why auto deduces the return type of GetFoo() as a value instead of a reference (which was your main question, sorry), consider this:

const Foo my_foo = GetFoo();

The above will create a copy, since my_foo is a value. If auto were to return an lvalue reference, the above wouldn't be possible.

why auto doesn't detect reference type of function

auto just by itself doesn't infer reference types. You can use auto& if you explicitly need a lvalue reference, or auto&& to use the reference collapsing rules for type inference.

If you need the type of an expression, use decltype. Keep in mind that there is a difference between decltype(x) and decltype((x)) -- the latter preserves references.

More resources on that topic:

  • Universal References in C++11
  • use of rvalue reference and auto
  • In C++, what expressions yield a reference type when decltype is applied to them?

variable declared by auto sometimes is by reference?

std::vector<bool> is a failure in the standard library, a vector<T> which is not a container of Ts.

Thus, it has significantly different behavior from all other instantiations of vector.

The specific wart you stumbled over is that it's member-type reference is a proxy class representing a reference to a single bool.

Which means that auto, which never deduces as a reference, deduced as that proxy-class, will behave as if it was a reference.

&v[4] won't work for getting a pointer to the bool at index 4, because vector<bool> is not a container of bool and the index-operator also returns those proxy-classes.

Naturally, vector<bool> has really special iterators which allow iterating over the bit-set, so using iterators only has the handicap that dereferencing an iterator also returns a proxy.

auto&& variable's are not rvalue 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&&

Observing weird behavior with 'auto' and std::minmax

This is one of those cases of where not to use auto as the type specifier. std::minmax returns a pair of references:

template< class T > 
std::pair<const T&,const T&> minmax( const T& a, const T& b );

That's what auto will deduce. But delta() returns a temporary. So when you write:

auto dim = std::minmax(gtl::delta(y, gtl::HORIZONTAL),
gtl::delta(y, gtl::VERTICAL));

dim is holding two dangling references. But when you write:

std::pair<int, int> dim1 = std::minmax(...);

You're just holding the values directly. That's why this works but auto doesn't. The extra conversion you're performing prevents you from holding dangling references.


Alternatively, and for completeness, you could use a different overload of minmax that doesn't return references:

template< class T >
std::pair<T,T> minmax( std::initializer_list<T> ilist);

which just involves some extra braces:

auto dim2 = std::minmax({gtl::delta(y, gtl::HORIZONTAL),
gtl::delta(y, gtl::VERTICAL)});

But I'd suggest just explicitly naming the type. That seems less error-prone to me.

What does auto&& tell us?

By using auto&& var = <initializer> you are saying: I will accept any initializer regardless of whether it is an lvalue or rvalue expression and I will preserve its constness. This is typically used for forwarding (usually with T&&). The reason this works is because a "universal reference", auto&& or T&&, will bind to anything.

You might say, well why not just use a const auto& because that will also bind to anything? The problem with using a const reference is that it's const! You won't be able to later bind it to any non-const references or invoke any member functions that are not marked const.

As an example, imagine that you want to get a std::vector, take an iterator to its first element and modify the value pointed to by that iterator in some way:

auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;

This code will compile just fine regardless of the initializer expression. The alternatives to auto&& fail in the following ways:

auto         => will copy the vector, but we wanted a reference
auto& => will only bind to modifiable lvalues
const auto& => will bind to anything but make it const, giving us const_iterator
const auto&& => will bind only to rvalues

So for this, auto&& works perfectly! An example of using auto&& like this is in a range-based for loop. See my other question for more details.

If you then use std::forward on your auto&& reference to preserve the fact that it was originally either an lvalue or an rvalue, your code says: Now that I've got your object from either an lvalue or rvalue expression, I want to preserve whichever valueness it originally had so I can use it most efficiently - this might invalidate it. As in:

auto&& var = some_expression_that_may_be_rvalue_or_lvalue;
// var was initialized with either an lvalue or rvalue, but var itself
// is an lvalue because named rvalues are lvalues
use_it_elsewhere(std::forward<decltype(var)>(var));

This allows use_it_elsewhere to rip its guts out for the sake of performance (avoiding copies) when the original initializer was a modifiable rvalue.

What does this mean as to whether we can or when we can steal resources from var? Well since the auto&& will bind to anything, we cannot possibly try to rip out vars guts ourselves - it may very well be an lvalue or even const. We can however std::forward it to other functions that may totally ravage its insides. As soon as we do this, we should consider var to be in an invalid state.

Now let's apply this to the case of auto&& var = foo();, as given in your question, where foo returns a T by value. In this case we know for sure that the type of var will be deduced as T&&. Since we know for certain that it's an rvalue, we don't need std::forward's permission to steal its resources. In this specific case, knowing that foo returns by value, the reader should just read it as: I'm taking an rvalue reference to the temporary returned from foo, so I can happily move from it.


As an addendum, I think it's worth mentioning when an expression like some_expression_that_may_be_rvalue_or_lvalue might turn up, other than a "well your code might change" situation. So here's a contrived example:

std::vector<int> global_vec{1, 2, 3, 4};

template <typename T>
T get_vector()
{
return global_vec;
}

template <typename T>
void foo()
{
auto&& vec = get_vector<T>();
auto i = std::begin(vec);
(*i)++;
std::cout << vec[0] << std::endl;
}

Here, get_vector<T>() is that lovely expression that could be either an lvalue or rvalue depending on the generic type T. We essentially change the return type of get_vector through the template parameter of foo.

When we call foo<std::vector<int>>, get_vector will return global_vec by value, which gives an rvalue expression. Alternatively, when we call foo<std::vector<int>&>, get_vector will return global_vec by reference, resulting in an lvalue expression.

If we do:

foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;

We get the following output, as expected:

2
1
2
2

If you were to change the auto&& in the code to any of auto, auto&, const auto&, or const auto&& then we won't get the result we want.


An alternative way to change program logic based on whether your auto&& reference is initialised with an lvalue or rvalue expression is to use type traits:

if (std::is_lvalue_reference<decltype(var)>::value) {
// var was initialised with an lvalue expression
} else if (std::is_rvalue_reference<decltype(var)>::value) {
// var was initialised with an rvalue expression
}


Related Topics



Leave a reply



Submit