Почему аргумент шаблона типа T

#c

Вопрос:

Здесь, например, b имеет тип intamp; , но f(b) решает f(int) :

 #include <type_traits>

template <typename T>
void f(T arg) {
    static_assert(std::is_reference<T>::value);   // fails
}

void g() {
  int a = 5;
  intamp; b = a;
  f(b);
}
 

Я знаю, что стандарт диктует это — я спрашиваю, почему? Что пойдет не так (или — станет «удивительным»), если ссылка не будет удалена?

Комментарии:

1. Это просто выбор языкового дизайна. То, как это происходит, имеет смысл, потому что шаблон функции должен быть шаблоном для удаления специализированных версий функции. Если void f(int arg) это способ , которым мы пишем функцию, которую можно передать intamp; , но которая будет копировать intamp; , имеет смысл, чтобы она была такой же, когда вы обобщаете эту функцию в шаблон функции.

2. @Джастин, я не хочу копировать аргумент. Для меня не имеет смысла, что правила вычета типа шаблона решают за меня, что я делаю. Чтобы уточнить: я знаю, как обойти это, я спрашиваю об обосновании дизайна.

3. Ссылка-это псевдоним. Все, что это есть, — это другое название для чего-то, что уже выходит. Это означает , что в f(b) , b может быть заменено на a . Что это за тип a ?

4. @NathanOliver еще в C int и intamp; являются различными типами, и выбор одного из них вместо другого в экземпляре шаблона имеет последствия (копирование).

5. Система типов значений C темна и очень-очень-очень неясна. Просто прими это. Если вы хотите использовать ссылки, сделайте свой аргумент функции ссылкой.

Ответ №1:

Здесь играют две вещи.

Что делать, если вы , как автор f , хотите написать функцию шаблона, в которой пользователь должен скопировать/переместить параметр в функцию?

В коде, не являющемся шаблоном, этот прототип будет выглядеть следующим образом: void f(SomeType st); . У любого вызывающего абонента f нет выбора, кроме как скопировать/переместить в параметр. Независимо от того, есть ли у них объект, ссылка на объект или что-то еще. st должен быть объектом, отдельным и отличным от остальных.

Вычет аргумента шаблона предназначен для того, чтобы по возможности выглядеть как версия без шаблона. Так что , если у вас есть template<typename T> void f(T t); , и вы вызываете его с f(some_object) помощью, это должно работать точно так же, как в нетемплированной версии.

Другое дело, что ссылочные переменные должны быть просто другим именем для какого-либо объекта. То есть a и b должно, при прочих равных условиях, вести себя одинаково. Поэтому f(a) следует делать то же самое, f(b) что и .

Правила вывода аргументов шаблона благоприятствуют обоим из них. Он сохраняет ссылки, ведущие себя точно так же, как объект, и позволяет функции шаблона вести себя как эквивалентно определенная версия без шаблона.

Если вам нужен ссылочный тип, вы должны отказаться от вычета аргументов шаблона и указать его напрямую: f<decltype((b))>(b) . Обратите внимание на использование здесь двойных скобок; это важно для того decltype , чтобы быть ссылкой.

Комментарии:

1. Спасибо! Одно замечание: действительно ли необходимы двойные парантезы? Это печатает 11 : std::cout << std::is_reference<decltype(b)>::value << std::is_reference<decltype((b))>::value;

Ответ №2:

Согласно правилам языка, f(b) это не вызов f с выражением типа intamp; . Он вызывает f значение типа lvalue int . То же самое произошло бы и в том случае, если бы вы это сделали f(a) .

В общем случае, когда ссылочная переменная упоминается по имени, это выражение является значением lvalue ссылочного типа. Тот факт, что переменная сама по себе является ссылкой, не виден системе типов.

Таким образом, f никогда не выводится T , чтобы иметь ссылочный тип. С f его точки зрения, аргумент на самом деле никогда не является ссылкой.

Комментарии:

1. Спасибо, но я еще не уверен, что понимаю: похоже, информация о b том, чтобы быть ссылкой, не потеряна и, похоже, видна системе типов. Например: cout << std::is_reference<decltype(b)>::value; напечатал 1 бы . Было ли какое-то явное решение игнорировать ссылочную часть типа в выводе типа шаблона?

2. @OfekShilon Когда ссылочная переменная является операндом decltype , напрямую и без каких-либо дополнительных скобок, вы можете заметить тот факт, что это ссылка. Вы также можете сделать это с decltype(auto) помощью . Иначе вы не сможете. Этот decltype случай является единственным исключением из общего правила.

3. Я думаю, что одинарные/двойные скобки здесь не имеют значения. Это печатает 11 : std::cout << std::is_reference<decltype(b)>::value << std::is_reference<decltype((b))>::value;

4. @OfekShilon Это имеет значение в случае не ссылочной переменной. decltype(a) есть int , пока decltype((a)) есть intamp; . Таким образом, используя одиночные круглые скобки, вы можете проверить их на соответствие. Как только вы переходите к двойным скобкам, вы запрашиваете свойства выражения , и разница между ссылочными и не ссылочными переменными стирается.

Ответ №3:

Что пойдет не так (или — станет «удивительным»), если ссылка не будет удалена?

Рассмотрим следующую функцию:

 template <typename T>
void f(T arg) {
    something = std::move(arg);

// ...
f(lvalue);            // copies    
f(str::move(lvalue)); // moves
 

Намерение состоит в том, чтобы скопировать значение из значения lvalue и перейти из значения rvalue.

Если T выводить на ссылку, то lvalue аргумент будет перенесен, что в данном случае нежелательно.


Я не хочу копировать этот аргумент.

  • Если вы пишете функцию, укажите ссылочный параметр с помощью символаamp;, а не параметр объекта. Таким образом, аргумент никогда не может быть скопирован в вызов (вы все равно можете скопировать его внутри функции).
  • Если вы вызываете функцию, которая принимает параметр объекта, то передайте значение rvalue, а не значение lvalue. Таким образом, вы будете копировать только в том случае, если перемещение является копией, и при этом не требуется копирование.

Для меня не имеет смысла, что правила вычета типа шаблона решают за меня, что я делаю

Вы решили использовать T . Вместо этого вы должны решить использовать Tamp; Tamp;amp; или const Tamp; , если вам нужна ссылка.