Почему эта функция возвращает ссылку 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 аргументы имеют имена, поэтому они являются lvalue. Теперь в игру вступают действительно сложные правила условного оператора, но Я думаю, что подходящая строка:

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

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

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


ИЗМЕНИТЬ:

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

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

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

Но если мы используем std::forward, мы превращаем второй и третий аргументы operator?: (назад) в значения r, а именно, значения x. Так как значения x по-прежнему являются значениями gl (вы не отстаёте?), применяется то же правило условного оператора, и мы получаем результат того же типа и категории значений: то есть int&&, которое является значением x.

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

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

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


person Tristan Brindle    schedule 14.10.2014    source источник
comment
Я думаю, что в теле вы можете использовать std::move, чтобы явно указать компилятору, что вам больше не нужны эти значения.   -  person Garrappachc    schedule 14.10.2014
comment
@Garrappachc Как уже отмечалось, я знаю решение (использовать std::forward, а не std::move, поэтому функция по-прежнему работает с аргументами lvalue). Мне любопытно, почему первое (неправильное) определение делает то, что делает.   -  person Tristan Brindle    schedule 14.10.2014
comment
Проблема может быть связана с правилами decltype(), если удаляемый возвращаемый тип (т.е. auto min(...) { ... }) и позволяющий компилятору вывести его, он возвращает int.   -  person Niall    schedule 14.10.2014
comment
@ Найл, я просто редактировал вопрос, чтобы задать что-то подобное! Спасибо :-)   -  person Tristan Brindle    schedule 14.10.2014
comment
Тип условного выражения не является значением rvalue ideone.com/ufbjIQ   -  person user657267    schedule 14.10.2014


Ответы (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.

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

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


Дополнительную информацию о std::forward и свертывании ссылок можно найти здесь, на SO.

person Niall    schedule 14.10.2014
comment
Обычный случай возврата auto немного вводит в заблуждение, потому что auto никогда не выводит ссылку (вот почему decltype(auto) существует в C++14). Но немного о том, что decltype формирует ссылку при задании аргумента lvalue, это именно тот ответ, который я искал, спасибо :-) - person Tristan Brindle; 14.10.2014
comment
Правда, с auto происходит гораздо больше, это был просто намек на то, где может быть проблема. - person Niall; 14.10.2014