What is the type of an 'auto' return type when returning *this in an anonymous class?

Viewed 2584

In this code:

struct
{
    auto operator[](const char*)
    {
        return *this;
    }

} m_some_class;

What is type of auto in here?

5 Answers

What is type of auto in here ?

The type is decltype(m_some_class) - I.e., the return value is of the same type as the variable m_some_class.


Note that the function will return a copy of *this.

If a reference to *this is wanted instead, you can use auto& or, since C++14, the more generic decltype(auto).

For anonymous structure types, internally the compiler creates a name and the auto in your case return the structure.

You can see below, that your anonymous structure is given name __anon_1_1 and the operator[] function returns object of __anon_1_1 structure. m_some_class is instance of type __anon_1_1

cppinsights website provides a way to understand

your code

struct
{
    auto operator[](const char*)
    {
        return *this;
    }

}m_some_class;

compiler version

struct __anon_1_1
{
  inline __anon_1_1 operator[](const char *)
  {
    return __anon_1_1(*this);
  }
  
  // inline constexpr __anon_1_1() noexcept = default;
  // inline constexpr __anon_1_1(const __anon_1_1 &) noexcept = default;
};

__anon_1_1 m_some_class = __anon_1_1();

The line in the given code:

return *this;

returns the struct m_some_class itself, i.e. the type of the operator[] is:

decltype(m_some_class); // i.e. the type returned is the same as the struct

Also, notice that this will only return a copy instance of the struct since the passed argument isn't given any reference-to operator. Any changes made to the copy of the struct won't affect the original struct.


What's the auto keyword?

The auto keyword is typically used in those situations when the type of something is not known to the programmer or it's too lengthy to type either.

Also, the type defined by auto may vary dependent upon the various situations. For instance:

auto len = vector.size(); // len is now defined as size_t in compile time

In some systems, the type of len maybe unsigned long and in my case, it's unsigned long long, here you can't explicitly define which qualifier to use correctly in this indeterminate place. Here we use auto keyword.

All standard references below refers to N4659: March 2017 post-Kona working draft/C++17 DIS.


TLDR: placeholder return type deduction

It is of no significance that the class is anonymous, as the return type is deduced solely from the return statement.

// Denote the type of the anonymous class as 'T'.

// Return type deduced to 'T'
auto operator[](const char*)
{
    return *this;
}

// Return type deduced to 'T&'
auto& operator[](const char*)
{
    return *this;
}

// Return type deduced to 'T&'
decltype(auto) operator[](const char*)
{
    return *this;
}

Details and relevant standard passages follows below.


Placeholder return type deduction: auto (C++11 and onwards)

From [expr.unary.op]/1 [extract, emphasis mine]:

[expr.unary.op]/1 The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points. [...]

Thus, the result of *this is an lvalue referring to the object onto which the operator call is invoked.

From [dcl.spec.auto]/1 and [dcl.spec.auto]/2 [extract, emphasis mine]:

[dcl.spec.auto]/1 The auto and decltype(auto) type-specifiers are used to designate a placeholder type that will be replaced later by deduction from an initializer. [...]

[dcl.spec.auto]/2 The placeholder type can appear with a function declarator [...] in any context where such a declarator is valid. [...] If the declared return type of the function contains a placeholder type, the return type of the function is deduced from non-discarded return statements [...].

From [dcl.type.auto.deduct]/2 and [dcl.type.auto.deduct]/4 [extract, emphasis mine]:

[dcl.type.auto.deduct]/2 A type T containing a placeholder type, and a corresponding initializer e, are determined as follows:

  • (2.1) for a non-discarded return statement that occurs in a function declared with a return type that contains a placeholder type, T is the declared return type and e is the operand of the return statement. If the return statement has no operand, then e is void();
  • [...]

[dcl.spec.auto]/4 If the placeholder is the auto type-specifier, the deduced type T' replacing T is determined using the rules for template argument deduction. [...]

[ Example:

const auto &i = expr;

The type of i is the deduced type of the parameter u in the call f(expr) of the following invented function template:

template <class U> void f(const U& u);

 — end example ]

Thus, the return type of the member operator function

auto operator[](const char*)
{
    return *this;
}

of the anonymous type, say, T, is the deduced type of the parameter u in the call f(*this) of the following invented function template:

template <class U> void f(U u);

where, as per above, *this is an lvalue, and the return type is thus deduced as T; namely the type of the anonymous class.

Using the same argument, the return of the member operator function

auto& operator[](const char*)
{
    return *this;
}

of the anonymous type, say, T, is T&.

As per the argument above it is of no significance that the class is anonymous, as the return type is deduced solely from the return statement.


Placeholder return type deduction: decltype(auto) (C++14 and onwards)

If we were to replace the placeholder return type auto with the placeholder type decltype(auto), different rules governs how the return type is determined.

decltype(auto) operator[](const char*)
{
    return *this;
}

From [dcl.type.auto.deduct]/5 [extract, emphasis mine]:

If the placeholder is the decltype(auto) type-specifier, T shall be the placeholder alone. The type deduced for T is determined as described in [dcl.type.simple], as though e had been the operand of the decltype.

And, from [dcl.type.simple]/4, [dcl.type.simple]/4.3 applies [extract]:

For an expression e, the type denoted by decltype(e) is defined as follows:

  • [...]
  • (4.4) otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;

as, per above, e (the return statement; *this) is a lvalue and neither [dcl.type.simple]/4.1, [dcl.type.simple]/4.2 nor [dcl.type.simple]/4.3 applies here.

Thus, the return type in the OP example modified using the decltype(auto) placeholder type is T&.

It is T, where T is the unnamed type of the class.

Even though it has no known name, the type does still exist, and can be "used" via mechanisms such as auto and decltype.

You probably want auto&, though.

Related