Почему эта функция возвращает ссылку на 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
восстановить "значение" аргументов, то есть изменить тело и decltype
сказать
t < u ? std::forward<T>(t) : std::forward<U>(u)
Однако я не могу объяснить, почему первое определение не приводит к ошибке.
Учитывая мое понимание переадресации и универсальных ссылок, оба t
а также u
вывести их типы аргументов как int&&
когда переданы целочисленные литералы. Тем не менее, в теле min
Аргументы имеют имена, поэтому они являются значениями. Теперь вступают в силу действительно сложные правила для условного оператора, но я думаю, что подходящая строка такова:
- Оба E2 [и] E3 являются glvalues одного типа. В этом случае результат имеет тот же тип и тип значения.
и, следовательно, тип возвращаемого значения operator?:
должно быть int&&
а разве не так? Однако (насколько я могу судить) и Clang, и g ++ имеют min(int&&, int&&)
возвращая ссылку lvalue int&
, что позволяет мне назначить результат.
Ясно, что в моем понимании есть пробел, но я точно не знаю, чего мне не хватает. Кто-нибудь может объяснить мне точно, что здесь происходит?
РЕДАКТИРОВАТЬ:
Как правильно указывает Найл, проблема здесь не в условном операторе (который возвращает lvalue типа int&&
как и ожидалось), но с decltype
, Правила для decltype
сказать
если категория значения выражения lvalue, тогда decltype указывает T&
поэтому возвращаемое значение функции становится int&& &
, который по правилам свертывания ссылок C++11 превращается в простой int&
(вопреки моему ожиданию int&&
).
Но если мы используем std::forward
переворачиваем второй и третий аргументы operator?:
(обратно) в rvalues - в частности, xvalues. Поскольку xvalues по-прежнему являются glvalues (вы не отставали от спины?), Применяется то же правило условного оператора, и мы получаем результат с тем же типом и категорией значений: int&&
который является xvalue.
Теперь, когда функция возвращается, она вызывает другой decltype
правило:
если категория значения выражения - xvalue, тогда decltype указывает T&&.
На этот раз ссылка рушится дает нам int&& && = int&&
и, что более важно, функция возвращает значение x. Это делает незаконным присваивать возвращаемое значение, как мы хотели бы.
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 (они являются lvalue - именованными ссылками r-значения), возвращаемое значение является ссылкой lvalue.
В этой ситуации это приводит к потенциальной ситуации, когда литерал может быть изменен. Тщательное использование пересылки должно применяться при использовании "универсальных ссылок" (или "ссылок пересылки") и связанных правил свертывания ссылок.
Как вы уже отмечали, для исправления ситуации нужно правильно использовать std::forward
должен быть применен, и тип возвращаемого значения будет ожидаемым.
Для получения дополнительной информации о std::forward
и ссылка сворачивается можно найти здесь, на SO.