Подтвердить что ты не робот

Почему эта функция возвращает ссылку lvalue при заданных аргументах rvalue?

Следующее определение функции min

template <typename T, typename U>
constexpr auto
min(T&& t, U&& u) -> decltype(t < u ? t : u)
{
    return t < u ? t : u;
}

имеет проблему: кажется, что законно писать

min(10, 20) = 0;

Это было протестировано с помощью Clang 3.5 и g++ 4.9.

Решение прост, просто используйте std::forward для восстановления "rvalue-ness" аргументов, т.е. измените тело и decltype на

t < u ? std::forward<T>(t) : std::forward<U>(u)

Однако я затрудняюсь объяснить почему первое определение не вызывает ошибки.


Учитывая мое понимание пересылки и универсальных ссылок, как t, так и u выводит их типы аргументов как int&& при передаче целочисленных литералов. Однако в теле min аргументы имеют имена, поэтому они являются lvalues. Теперь действительно сложные правила для условного оператора вступают в игру, но я думаю, что соответствующая строка:

  • Оба E2 [и] E3 являются glvalues ​​того же типа. В этом случае результат имеет тот же тип и категорию значений.

и, следовательно, возвращаемый тип operator?: должен быть int&&, если это не так? Однако (насколько я могу судить), как Clang, так и g++ имеют min(int&&, int&&), возвращающий ссылку lvalue int&, что позволяет мне присваивать результат.

Ясно, что в моем понимании есть пробел, но я не уверен, что это такое, чего я не вижу. Может ли кто-нибудь объяснить мне, что именно происходит здесь?


EDIT:

Как верно указывает Найл, проблема здесь не в условном операторе (который возвращает значение l типа типа int&&, как ожидалось), но с decltype. Правила для decltype говорят

если категория значений выражения равна lvalue, тогда decltype указывает T &

поэтому возвращаемое значение функции становится int&& &, которое по правилам сбрасывания ссылок С++ 11 превращается в обычный int& (вопреки моему ожидаемому int&&).

Но если мы используем std::forward, мы поворачиваем второй и третий аргументы operator?: (назад) на rvalues ​​- в частности, xvalues. Поскольку xvalues ​​по-прежнему являются glvalues ​​(вы поддерживаете спину?), Применяется одно и то же правило условного оператора, и мы получаем результат того же типа и категории значений, то есть int&&, который является значением x.

Теперь, когда функция возвращается, она запускает другое правило decltype:

если категория значений выражения равна xvalue, тогда decltype указывает T &&

На этот раз сбрасывание ссылок дает нам int&& && = int&& и, что более важно, функция возвращает значение x. Это делает незаконным присвоение возвращаемого значения, как хотелось бы.

4b9b3361

Ответ 1

Проблема может быть с правилами decltype().

Это намек; если удаляется возвращаемый тип возврата

template <typename T, typename U>
constexpr auto
min(T&& t, U&& u)
{
    return t < u ? t : u;
}

и компилятору разрешено его выводить, тип возврата int.

Использование decltype

decltype ( expression )
...
если категория значений выражения равна lvalue, тогда decltype указывает T &

Взято из cppreference.

Поскольку выражение, включающее t и u, является значением lvalue (они являются lvalues ​​- с именами r-значений), return является ссылкой lvalue.

В этой ситуации это приводит к потенциальной ситуации, когда литерал может быть изменен. Тщательное использование пересылки необходимо применять при использовании "универсальных ссылок" (или "ссылки пересылки" ) и связанные с ним правила сворачивания ссылок.

Как вы уже отметили, для исправления ситуации необходимо использовать правильное использование std::forward, и тип возврата будет ожидаемым.


Подробнее о std::forward и о сворачивании ссылок можно найти здесь, на SO.