C++ Auto VS Auto&

What is the difference between auto and auto&?

Auto, for(auto x : range): This usage will create a copy of each element of the range.

Auto &, for(auto& x : range): when you want to modify the elements in range (without proxy class reference processing), use auto&.

ii is a reference, so in the loop body, if ii is modified, the corresponding element in a will also be modified.

jj is not a reference. In each loop, it is a copy of the corresponding element. Its modification will not affect the corresponding element in a. Since each loop creates a copy, it will bring system overhead.

If you want to ensure that the data in a is not modified and you want to improve the efficiency, you can use the form const auto & ii : a.

C++ auto vs auto&

The type deduction for auto works exactly the same as for templates:

  • when you deduce auto you will get a value type.
  • when you deduce auto& you wil get a non-const reference type
  • when you deduce const auto& you will get a const reference
  • when you deduce auto&& you will get
    • a non-const reference if you assign a non-const reference
    • a const reference if you assign a const reference
    • a value when you assign a temporary

auto and auto& and const

Do I understand correctly that variables declared with auto (x1) will never be const

Correct.

When with auto&(x2) will be const if exp2 will be const.

A reference is never const; references cannot be cv qualified. x2 could be a reference to const.

auto it = find(cont.cbegin(), cont.cend(), value);

Here despite I use cbegin and cend it will be non-const iterator

it would be a non-const qualified object of const_iterator type.

const auto it1 = find(cont.cbegin(), cont.cend(), value);

it1 would be a const qualified object of const_iterator type.

Indirecting through a const_iterator (typically) gives you a reference to const, and thus you cannot modify the pointed object.

A const object cannot (generally) be modified. Thus, you for example cannot increment a const qualified iterator.

Using auto vs const auto& to temporary keep result of method call

Is that correct?

Yes.

If so, should I prefer const auto& over auto for such cases all the time?

It depends on what you intend to do.

If you want a copy, then use an object variable. If you want to refer to an object stored elsewhere, then use a reference variable.

Also, should I prefer getCRef() interface over getValue() when I can guarantee that reference lives as long as underlying object does?

Same as above.

my intention is to pass unmodified result of one method call to another

let's assume that object doesn't change for the duration

If T is trivial, then prefer object to avoid unnecessary indirection. If T is non-trivial, then prefer reference to avoid copying. If you don't know whether T is trivial, then assume that it is not.

Which one is better, auto&&, auto or auto const

It really depends on your needs and what you're iterating over.

Styles 1 and 3 make a copy of the element. This means you can't change the original container. For small types it may be beneficial in terms of performance (benchmark yourself on your target architecture), but usually is just a bad default.

You then have to decide whether you're one of the const by default folks. If yes, take style 3.


Style 2 is indeed most generic, but not necessarily in the good way. Unless you want to further move from the element, there's little point to taking a non-const reference to a sequence generated on the spot, i.e.:

for(auto&& x : std::vector<int>{1,2,3})
{
// x = 42; ???
}

If you look at C++ Core Guidelines here and here, you'll see no examples with auto&& and they suggest taking const& by default unless you're going to modify the variable. That's what I'd stick to.

Difference between const auto & and auto & if object of reference is const

auto keyword automatically decides the type of the variable at compile time.

In your first case, auto is reduced to int where it's reduced to const int in the second case. So, both of your cases are reduced to the same code as:

const int &k = i;

However, it's better to have the const explicitly for better readability and to make sure your variable TRULY is const.

Difference between capturing return value by `const auto&` and `auto`

In the shown example, there is no reason to use a reference. The behavior of the snippet is identical with or without it.

In general, it's a good idea to use a reference to avoid incurring needless copies, so there's no harm in getting into the habit of using references, even if it doesn't make a difference in some cases.

I'd suggest writing const on the east though. This is just a preference, and definitely doesn't change the meaning of the code, but it's more consistent. I would write it as:

auto const & now = time(nullptr);

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