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