#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;
, если вам нужна ссылка.