The following program is, as expected, accepted by both GCC, Clang and MSVC (for various compiler and language versions):
// #g.1: rvalue reference function parameter
constexpr bool g(int&&) { return true; }
// #g.2: const lvalue reference function parameter
constexpr bool g(int const&) { return false; }
static_assert(g(0), ""); // OK: picks #g.1
The similar case but for overloads over an array {rvalue, const lvalue} reference parameter is, however, rejected by GCC, whilst it is accepted by Clang and MSVC:
#include <cstddef>
// #f.1: rvalue ref overload
template<std::size_t size>
constexpr bool f(int (&&)[size]) { return true; }
// #f.2: lvalue ref overload
template<std::size_t size>
constexpr bool f(int const (&)[size]) { return false; }
static_assert(f({1, 2, 3}), ""); // Clang: OK (picks #f.1)
// MSVC: OK (picks #f.1)
// GCC: Error (picks #f.2)
DEMO.
At first glance this looks like a GCC bug, but I'd like to be sure.
Since the argument is an initializer list, special rules apply, as per [over.ics.list]/1:
When an argument is an initializer list ([dcl.init.list]), it is not an expression and special rules apply for converting it to a parameter type.
As the parameter type for both overloads are references types, [over.ics.list]/9 applies [emphasis mine]:
Otherwise, if the parameter is a reference, see [over.ics.ref].
[Note 2: The rules in this subclause will apply for initializing the underlying temporary for the reference. — end note]
Albeit non-normative, this means that the call
f({1, 2, 3})
for both of the candidate functions is (as per [over.ics.list]/6) as if
template<typename T>
using type = T;
f(type<int[3]>{1, 2, 3}); // prvalue array
[over.ics.ref], particularly [over.ics.ref]/3, simply covers that this kind of prvalue argument can bind directly both to an rvalue reference or to a const lvalue reference, meaning both candidates are viable candidates.
We turn to [over.ics.ran]/3.2.3:
/3.2 Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if
- [...]
- /3.2.3 S1 and S2 include reference bindings ([dcl.init.ref]) and neither refers to an implicit object parameter of a non-static member function declared without a ref-qualifier, and S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference
Thus, GCC is wrong to pick the const lvalue reference overload #f.2
, as #f.1
is the best viable function.
We may note that all compilers agree on the #f.1
overload if we explicitly pass an rvalue array instead of using an initializer list (DEMO):
template<typename T>
using type = T;
// All compilers agree: OK
static_assert(f(type<int[3]>{1, 2, 3}));
GCC's invalid behavior in the original example is thus arguably specifically due to how GCC treats initializer list arguments for candidate functions whose corresponding function parameter type is of array reference type.