Search code examples
c++arraysc++20return-typedecltype

Returning an array from a function declared with decltype(auto)?


I would like to return an array (or reference to an array) from a function as follows:

decltype(auto) bar() { static int a[2]; return a; }

Unfortunately, it results in very cryptic errors. GCC complains:

error: cannot convert 'int [2]' to 'int [2]' in return

and Clang is not better in explaining the problem:

error: array initializer must be an initializer list

Demo: https://gcc.godbolt.org/z/ao7Txa9oP

Is it even possible to return an array from a function? (I am fine with getting rid of decltype(auto).)

If it is not, then why, provided that functions can accept arrays in various ways, e.g.:

void f(auto [2]);
void f(auto (&)[2]);

Solution

  • The problem is indeed decltype(auto). The return type is deduced by following this list (the expression in question is the one given to the return statement):

    [dcl.type.decltype] (redacted for emphasis)

    decltype-specifier:
        decltype ( expression )
    

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

    • ...
    • ...
    • otherwise, if E is an unparenthesized id-expression or an unparenthesized class member access ([expr.ref]), decltype(E) is the type of the entity named by E. If there is no such entity, or if E names a set of overloaded functions, the program is ill-formed;
    • ...
    • ...

    The type of a is int[2]. So you are essentially defining a function with an array return type... but...

    [dcl.fct]

    11 Functions shall not have a return type of type array or function, although they may have a return type of type pointer or reference to such things. There shall be no arrays of functions, although there can be arrays of pointers to functions.

    You produced an ill-formed function type inadvertently. So yes the solution, as you noted, is to not use decltype. You can explicitly return a reference (to a deduced array type), since this is really what you want:

    auto& bar() { static int a[2]; return a; }
    

    or if you want to be explicit about the return type, without entering declarator hell, you can specify it in a trailing fashion:

    auto bar() -> int(&)[2] { static int a[2]; return a; }
    

    Either way, I'd say it's better than relying on the (at times cryptic) semantics of decltype. There are no subtle bugs when you explicitly return an lvalue reference (when that is your entire intention).


    As an aside, your last question contains declarations that are not specified as legal yet (though accepted by implementations since it is pretty much agreed they should be). This is CWG Issue 2397. But the gist of it is that it behaves as one would expect an array type in a function parameter. auto [2] is adjusted to auto*, while auto(&) [2] only binds to arrays of specific type. This is meant to be an abbreviated function template, equivalent to:

    template<typename T>
    void f(T[2]);
    
    template<typename T>
    void f(T(&)[2]);
    

    Return types are simply not adjusted like parameters are. Where parameter type adjustment itself is a C heritage thing, from which we got raw arrays that are not first-class citizens. They are pretty irregular as types go. If you need "saner" value-semantics with arrays, you have std::array for this purpose exactly.