85

Please take a look at the following simple code:

class Foo
{
public:
  Foo(){}
  ~Foo(){}

  Foo(const Foo&){}
  Foo& operator=(const Foo&) { return *this; }
};

static Foo g_temp;
const Foo& GetFoo() { return g_temp; }

I tried to use auto like this:

auto my_foo = GetFoo();

I expected that my_foo will be a constant reference to Foo, which is the return type of the function. However, the type of auto is Foo, not the reference. Furthermore, my_foo is created by copying g_temp. This behavior isn't that obvious to me.

In order to get the reference to Foo, I needed to write like this:

const auto& my_foo2 = GetFoo();
      auto& my_foo3 = GetFoo();

Question: Why does auto deduce the return type of GetFoo as an object, not a reference?

Nate Kohl
  • 35,264
  • 10
  • 43
  • 55
minjang
  • 8,860
  • 9
  • 42
  • 61

2 Answers2

67

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.

someguy
  • 7,144
  • 12
  • 43
  • 57
  • 14
    You did not explain why the ref-qualifier gets removed too. – Lightness Races in Orbit Aug 21 '11 at 14:03
  • 3
    @Tomalak Geret'kal: You mean why they've decided to do this? It makes sense, doesn't it? Consider this: `Foo my_foo = GetFoo();` and that `GetFoo()` didn't return a const type. It would be the same as: `auto my_foo = GetFoo();`. If auto included the reference as well, you wouldn't be able to do the aforementioned. – someguy Aug 21 '11 at 14:15
  • 9
    Don't tell me; put it in your answer. – Lightness Races in Orbit Aug 21 '11 at 14:28
  • 3
    Working link to article http://aristeia.com/Papers/appearing%20and%20disappearing%20consts.pdf – kaihowl May 14 '14 at 13:34
  • 1
    Scott Meyers, Effective Modern C++ book dedicates an entire chapter to explain this. A must read. – dehinrsu Dec 12 '19 at 05:49
  • I really don't know how this answer the question. You quote examples from the article, which don't really portray the OP case. You are speculating about `const`, then you speculate about `const Foo` and `Foo` and what would have happened if `auto` returned lvalue reference, but you never provided any clear answer why `auto` has deduced `Foo` as value. – mip Jan 09 '23 at 20:21
  • Hi @mip. It's been a while since I answered this question (nor have I touched C++ since that period), but I basically wanted to answer two things: why the `const` qualifier was dropped and why I think `auto` does not infer an lvalue reference. I cited that article because it talks about how type deduction for `auto` is similar to that of template parameters, and it gives some examples. It also explains that the const qualifier is inferred if you do `auto& my_foo2 = GetFoo()`. What do you mean I 'speculated'? – someguy Jan 25 '23 at 01:28
  • 1
    Honestly, if I were to write the answer again, I would reword it significantly, but I'd say the information is there. The answer to OP's question is somewhat buried I suppose, but what I wanted to say is that if `auto` without the ampersand inferred an lvalue reference, you wouldn't be able to write `const Foo my_foo = GetFoo()` using `auto`. – someguy Jan 25 '23 at 01:31
0

You can take simple answer as granted by MSVC Technical documentation:

Using auto drops references, const qualifiers, and volatile qualifiers.

You don't need a function to achieve similar results. Consider:

int var;
const int & cref = var;
auto avar = cref;

The type of avar will be int not const int & nor int &.

To go a bit deeper into this we can go to cppreference.com and refer to template argument deduction analogy.

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).

We tend to intuitively use templates and until it works we never really bother about template argument deduction rules. These rules are in fact quite complex. But we can make the "imaginary template for auto" non-imaginary and test what would have happened if we provided auto i = expr; to the template. In our case the template may look like this:

template<class U> 
void f(U u)
{
    cout << type_name<decltype(u)>() << "\n";
}

Let's feed it with variables with types of our interest:

    int i = 0;
    int & ir = i;
    const int & cir = i;
    f(i);
    f(ir);
    f(cir);

And the what's at the output?

int
int
int

The same thing would have happened with auto as predicted by MSVC docs :)

Here's the code if you wanted to play.


Here's also example from MS website if it disappeared one day

// cl.exe /analyze /EHsc /W4
#include <iostream>

using namespace std;

int main( )
{
    int count = 10;
    int& countRef = count;
    auto myAuto = countRef;

    countRef = 11;
    cout << count << " ";

    myAuto = 12;
    cout << count << endl;
}

In the previous example, myAuto is an int, not an int reference, so the output is 11 11, not 11 12 as would be the case if the reference qualifier hadn't been dropped by auto

mip
  • 8,355
  • 6
  • 53
  • 72