I was playing around with auto
in std::pair
. In the below code, function f
is supposed to return a std::pair
of types which depend on a template parameter.
A working example:
EXAMPLE 1
template <unsigned S>
auto f()
{
if constexpr (S == 1)
return std::pair{1, 2}; // pair of ints
else if constexpr (S == 2)
return std::pair{1.0, 2.0}; // pair of doubles
else
return std::pair{0.0f, 0.0f}; // pair of floats
}
This works with gcc 9.2, gcc 10.0, clang 9.0 and clang 10.0.
Next, I wanted to explicitly write the return type as a std::pair
for clarity reasons:
EXAMPLE 2
template <unsigned S>
std::pair<auto, auto> f()
{
if constexpr (S == 1)
return {1, 2};
/* ... */
}
Both gcc 9.2/10.0 and clang 9.0/10.0 failed to compile this.
gcc 9.2
error: invalid use of 'auto'
error: template argument 1 is invalid // first argument (auto) of std::pair
error: template argument 2 is invalid // second argument (auto) of std::pair
error: cannot convert '<brace-enclosed initializer list>' to 'int' in return
From the last error message, gcc 9.2 seems to believe that std::pair<auto, auto>
is an int
. How can this be explained?
gcc 10.0
error: returning initializer list
This error is understandable, however, I expected the constructor of std::pair
to be invoked, or is there something I am missing here?
clang 9.0 and 10.0
'auto' not allowed in template argument
excess elements in scalar initializer
no matching function for call to 'f'
Ok, clang doesn't like any of this. From the second error message, it seems that clang also believes the return type is int
.
Finally, to fix the error obtained compiling with gcc 10.0, I decided to return a std::pair
explicitly:
EXAMPLE 3
template <unsigned S>
std::pair<auto, auto> f()
{
if constexpr (S == 1)
return std::pair{1, 2};
/* ... */
}
clang 9.0 and 10.0
Same as before, but with an additional:
no viable conversion from returned value of type 'std::pair<int, int>' to function return type 'int'
Here clang still thinks we are returning an int
?
gcc 9.2
Same as before.
gcc 10.0
It works!
I guess some features still have to be implemented, or in one of the situations described above, is there a compiler which is right and the other wrong? In my opinion, example 2 should work. Or should it not?
In C++14, you can just use auto as a return type.
C++: “auto” return type deduction The “auto” keyword used to say the compiler: “The return type of this function is declared at the end”. In C++14, the compiler deduces the return type of the methods that have “auto” as return type.
decltype(auto) is primarily useful for deducing the return type of forwarding functions and similar wrappers, where you want the type to exactly “track” some expression you're invoking.
If a return type isn't specified, the C compiler assumes a default return type of int .
The syntax:
std::pair<auto, auto> f() { return std::pair(1, 2); }
~~~~~~~~~~~~~~~~~~~~~
Was part of the original Concepts TS but was not included in the Concepts proposal that is part of C++20. As such, the only placeholder types in C++20 are auto
(and variations thereof like auto**
), decltype(auto)
, and constrained placeholders (Concept auto
and variations thereof). This kind of nested placeholder type would be very useful, but is not part of C++20, so that function declaration is ill-formed.
Now, gcc allows it because gcc implemented the Concepts TS and I guess they decided to keep this feature. clang never implemented the TS, so it doesn't.
Either way, this:
std::pair<auto, auto> f() { return {1, 2}; }
Would always be ill-formed. The meaning of the syntax is that we deduce the return type and then require that it matches pair<T, U>
for some types T
and U
. We're basically trying to invoke the invented function:
template <typename T, typename U>
void __f(std::pair<T, U>);
__f({1, 2}); // this must succeed
But you cannot deduce a type from {1, 2}
- a braced-init-list doesn't have a type. Perhaps this is something that should be explored (as it's easy to understand at least in a simple case like this), but it has never been allowed. So rejecting it is correct either way.
Lastly:
gcc 9.2 seems to believe that
std::pair<auto, auto>
is anint
. How can this be explained?
For some reason (probably due to our C legacy with implicit int
), when gcc does not recognize or understand a type, it just uses int
as the placeholder in error messages. This is super confusing, because obviously it's gcc that came up with int
and not the source code. But that's the way it is.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With