Почему эта функция возвращает ссылку на 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.

Другие вопросы по тегам