Я начал изучать C++, и в настоящее время я пытаюсь начать работу с шаблонами, поэтому, пожалуйста, потерпите меня, если моя формулировка не на 100% точна.
Я использую следующую литературу:
В первой книге рассматривается следующая функция шаблона
template<typename T1, typename T2>
auto max(T1 a, T2 b) -> decltype(b<a?a:b) {
return b < a ? a : b;
}
и заявляет, что у этого определения есть недостаток, поскольку T1 или T2 могут быть ссылкой, так что возвращаемый тип может быть ссылочным типом.
Однако во второй книге говорится, что если ParamType, в нашем случае T1 и T2 не является ни указателем, ни ссылкой, что верно для нашего случая, то ссылочная часть вызывающего выражения игнорируется.
Проиллюстрировано на примере
template<typename T>
void f(T param);
int x = 27; // as before
const int cx = x; // as before
const int& rx = x; // as before
f(x); // T's and param's types are both int
f(cx); // T's and param's types are again both int
f(rx); // T's and param's types are still both int
Теперь мне интересно, как вообще возможно, что возвращаемый тип первого фрагмента кода является ссылочным типом?
@polygamma Это не будет выводиться как таковое, но кто-то может указать это, как это сделал Джарод в своем комментарии.
Выражение никогда не имеет ссылочного типа в C или C++ (да, в C нет ссылок, но система типов работает так же)





Они оба правы:
См. код, сгенерированный в cppinsights.
template<typename T1, typename T2>
auto max(T1 a, T2 b) -> decltype(b<a?a:b) {
return b < a ? a : b;
}
template<typename T1, typename T2>
auto max2(T1 a, T2 b){
return b < a ? a : b;
}
max(j,i);
max2(j,i);
Будет «генерировать»:
template<>
int & max<int, int>(int a, int b)
{
return b < a ? a : b;
}
template<>
int max2<int, int>(int a, int b)
{
return b < a ? a : b;
}
Проблема в C++11 -> decltype(b<a?a:b) если его удалить (в C++14 и более), функция больше не будет возвращать ссылку
static_assert( is_same_v<decltype(i),int> );
static_assert( is_same_v<decltype((i)),int&> );
static_assert( is_same_v<decltype(i+j),int> );
static_assert( is_same_v<decltype(true?i:j),int&> );
См. https://en.cppreference.com/w/cpp/language/operator_other#Conditional_operator
4) If E2 and E3 are glvalues of the same type and the same value category, then the result has the same type and value category [...]
5) Otherwise, the result is a prvalue [...]
В С++ это означает:
static_assert( is_same_v<decltype(true?i:j),int&> ); // E2 and E3 are glvalues
static_assert( is_same_v<decltype(true?i:1),int> ); // Otherwise, the result is a prvalue
Результат ?: не всегда равен xvalue. Категория зависит от типов и категорий значений операндов. Я думаю, что a ? a : b - это lvalue.
@eerorika 'троичное условное выражение для немного b и c'. Вы правы, я неправильно прочитал
@PasserBy Здравствуйте, должен ли я стереть «возврат ...», даже если стереть, он будет делать комментарии без цели?
@Martinm Да, комментарии нужны для улучшения поста. После этого комментарий может быть удален его владельцем. И людей к этому призывают. (Я удалю этот комментарий через 10 минут)
@MartinMorterol для случая decltype, в чем проблема, если функция возвращает ссылку? Можете ли вы объяснить, из книги не могу понять, почему это большая проблема.
@Daemon Если вы запустите ссылку на cppinsights, вы получите это предупреждение warning: reference to stack memory associated with parameter 'a' returned [-Wreturn-stack-address], которое довольно явно;). При необходимости вы можете посмотреть здесь: stackoverflow.com/questions/6441218/…
since T1 or T2 might be a reference, so that the return type could be a reference type.
Я предполагаю, что это неправильно процитировано. Тип возвращаемого значения может быть ссылочным, но по разным причинам.
Если вы пойдете немного дальше в своем вторая книга. В Пункт 3 вы найдете ответ на свой вопрос.
Applying decltype to a name yields the declared type for that name. Names are typically lvalue expressions, but that doesn’t affect decltype’s behavior. For lvalue expressions more complicated than names, however, decltype generally ensures that the type reported is an lvalue reference. That is, if an lvalue expression other than a name has type T, decltype reports that type as T&.
There is an implication of this behavior that is worth being aware of, however. In
int x = 0;x is the name of a variable, so decltype(x) is int. But wrapping the name x in parentheses—“(x)”—yields an expression more complicated than a name. Being a name, x is an lvalue, and C++ defines the expression (x) to be an lvalue, too. decltype((x)) is therefore int&. Putting parentheses around a name can change the type that decltype reports for it!
Теперь остался единственный вопрос:
Является ли b<a?a:b lvalue?
От cppreference
4) If E2 and E3 are glvalues of the same type and the same value category, then the result has the same type and value category, and is a bit-field if at least one of E2 and E3 is a bit-field.
Итак, a и b являются lvalue, и если они одного типа, b<a?a:b также будет lvalue. Смотрите хорошее объяснение здесь.
Это означает, что возвращаемый тип для вызова max(1, 2) будет int&, а для max(1, 2.0) будет int
ИМО тот факт, что decltype делает две разные вещи (которые лишь немного отличаются и не всегда разные), является мерзостью в языковом дизайне. OTOH static раньше считался ужасным выбором дизайна, но на самом деле он делает две совершенно разные вещи (верхний уровень и функциональный блок), а затем две тесно связанные вещи (статическую переменную класса и статическую переменную функции).
Большое спасибо за ваш ответ. Я просто хочу затронуть одну вещь. Вы написали: "Я предполагаю, что это неправильно процитировано", точная цитата из книги: "Может случиться так, что возвращаемый тип является ссылочным типом, потому что при некоторых условиях T может быть ссылкой"
max<const int&, const int&>(42, 51).