用于重载数组引用参数的编译器差异

Compiler variance for overloading over array reference parameters

正如预期的那样,以下程序被 GCC、Clang 和 MSVC(针对各种编译器和语言版本)所接受:

// #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

类似的情况,但是对于 array {rvalue, const lvalue} 引用参数的重载被 GCC 拒绝,而它被 Clang 和 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.

乍一看这像是一个 GCC 错误,但我想确定一下。

问题

GCC 错误

由于参数是一个初始化列表,根据 [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.

由于两个重载的参数类型都是引用类型,[over.ics.list]/9适用[强调我的]:

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]

尽管non-normative,这意味着调用

f({1, 2, 3})

两个候选函数(根据[over.ics.list]/6)就好像

template<typename T>
using type = T;

f(type<int[3]>{1, 2, 3});  // prvalue array

[over.ics.ref], particularly [over.ics.ref]/3,简单地涵盖了这种纯右值参数可以直接绑定到右值引用或常量左值引用,这意味着两个候选者都是可行的候选者。

我们转向[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

因此,GCC 选择 const 左值引用重载 #f.2 是错误的,因为 #f.1 是最可行的函数。


我们可能会注意到,如果我们显式传递右值数组而不是使用初始化列表 (DEMO),所有编译器都同意 #f.1 重载:

template<typename T>
using type = T;

// All compilers agree: OK
static_assert(f(type<int[3]>{1, 2, 3}));

GCC 在原始示例中的无效行为因此可以说是由于 GCC 如何处理候选函数的初始值设定项列表参数,其对应的函数参数类型是数组引用类型。


错误报告

  • Bug 104996 - 对 rvalue/const 左值数组引用参数的重载解析。列表参数错误地选择了 const 左值引用。过载