Возможно ли, чтобы параметр шаблона был ссылочным типом? - PullRequest
8 голосов
/ 18 апреля 2019

Я начал изучать C ++, и в настоящее время я пытаюсь начать работу с шаблонами, поэтому, пожалуйста, потерпите меня, если моя формулировка не на 100% точна.

Я пользуюсь следующей литературой:

  • Шаблоны C ++: Полное руководство (2-е издание)
  • Эффективный современный C ++: 42 специальных способа улучшить использование C ++ 11 и C ++ 14

В первой книге рассматривается следующая функция шаблона

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

Теперь мне интересно, как это вообще возможно, что тип возврата первого фрагмента кода является ссылочным типом?

Ответы [ 2 ]

5 голосов
/ 18 апреля 2019

Они оба правы:

См. Код, сгенерированный в 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) Если E2 и E3 являются glvalues ​​одного и того же типа и той же категории значения, то результат имеет тот же тип и категорию значения [...]

5) В противном случае результатом является значение [...]

В C ++ это означает:

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
1 голос
/ 18 апреля 2019

, поскольку T1 или T2 могут быть ссылкой, так что тип возвращаемого значения может быть ссылочным типом.

Я предполагаю, что это неправильно указано в кавычках.Тип возврата может быть ссылочным типом, но по разным причинам.

Если вы пойдете немного дальше в своей второй книге Item 3 вы найдете ответ на ваш вопрос.

Применение decltype к имени дает объявленный тип для этого имени.Имена, как правило, являются lvalue выражениями, но это не влияет на поведение decltype. Для выражений lvalue, более сложных, чем имена, однако, decltype обычно гарантирует, что сообщаемый тип является ссылкой на lvalue.То есть, если выражение lvalue, отличное от имени, имеет тип T, decltype сообщает, что этот тип имеет вид T & .

Однако, есть смысл этого поведения, о котором стоит знать.В

int x = 0;

x - имя переменной, поэтому decltype (x) - это int.Но завершение имени x в скобках - «(x)» - дает выражение более сложное, чем имя.Будучи именем, x является lvalue, а C ++ также определяет выражение (x) как lvalue.Следовательно, decltype ((x)) имеет тип int &.Скобки вокруг имени могут изменить тип, который decltype сообщает для него!

Теперь остается только один вопрос:

Является ли b<a?a:b lvalue?

С cppreference

4) Если E2 и E3 являются glvalues ​​одного и того же типа и той же категории значения, то результат имеет тот же тип и значениеcategory, и является битовым полем, если хотя бы одно из E2 и E3 является битовым полем.

Так что a и b являются lvalues ​​и если они относятся к одному типу b<a?a:b также будет lvalue.См. Хорошее объяснение здесь .

Это означает, что тип возврата для вызова max(1, 2) будет int&, а для max(1, 2.0) будет int

...