Следующее определение функции 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. Это делает незаконным присвоение возвращаемого значения, как хотелось бы.