Следующая программа, как и ожидалось, принимается как 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
Аналогичный случай, но для перегрузок по ссылочному параметру множество {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)
ДЕМО.
На первый взгляд это похоже на ошибку GCC, но я хотел бы быть уверен.
В вашем первом примере у вас есть const T&&
вместо просто T&&
. Обычно не имеет смысла иметь ссылку const rvalue, так как (1) T&&
, но не const T&&
, является пересылающей/универсальной ссылкой, и (2) фактическая ссылка rvalue на объект обычно используется, когда кто-то хочет взять ресурсы из этот объект и, таким образом, изменить его.
@jjramsey Я знаю, я намеренно добавил const в первый пример, чтобы сделать его ссылкой на rvalue (отредактируйте на основе наблюдения user177.. выше: я ошибочно подумал, что #f.1 был ссылкой на пересылку; это был минимальный набор на телефоне исправить, чтобы изменить эталонный пример #g.1 на перегрузку rvalue).
@jjramsey Вернулся на компьютер, поэтому обновил вопросы и ответы, чтобы удалить ненужный параметр шаблона типа.
Поскольку аргумент является списком инициализаторов, применяются специальные правила согласно [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]
Хотя это и ненормативно, это означает, что вызов
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], в частности [over.ics.ref]/3, просто указывает на то, что этот тип аргумента prvalue может напрямую связываться как со ссылкой rvalue, так и со ссылкой const lvalue, что означает, что оба кандидата являются жизнеспособными кандидатами.
Переходим к [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 lvalue #f.2
, поскольку #f.1
является наилучшей жизнеспособной функцией.
Мы можем заметить, что все компиляторы соглашаются с перегрузкой #f.1
, если мы явно передаем массив rvalue вместо использования списка инициализаторов (ДЕМО):
template<typename T>
using type = T;
// All compilers agree: OK
static_assert(f(type<int[3]>{1, 2, 3}));
Таким образом, недопустимое поведение GCC в исходном примере, возможно, связано именно с тем, как GCC обрабатывает аргументы списка инициализаторов для функций-кандидатов, чей соответствующий тип параметра функции имеет тип ссылки на массив.
Возможное исправление: github.com/gcc-mirror/gcc/compare/master...ecatmur:pr-104996
@ecatmur Хорошо! Является ли "Пожалуйста, проверьте и сообщите". запрос отчета об ошибке, предназначенный для других сопровождающих GCC или для меня как автора отчета об ошибке? (У меня нет опыта проверки исправлений GCC).
Да, надеюсь, у вас есть более сложный тестовый пример, с которым вы можете попробовать? (Ничего страшного, если нет, конечно). Сборка gcc в наши дни относительно проста; см. gcc.gnu.org/wiki/УстановкаGCC
@ecatmur У меня нет более сложного варианта использования, чем пример отчета об ошибке (резюмированный Патриком), но я рад попробовать на выходных в качестве поучительного упражнения.
@ user17732522 Вы правы, спасибо. Обновлены вопросы и ответы.