Почему это не работает, когда lvalue-ref-arg и rvalue-ref-arg передаются как один и тот же тип forwarding-ref?
#include <type_traits>
template<typename T>
void f(T&& a, T&& b)
{}
int main()
{
int n;
f(n, std::move(n));
}
T&&
является ссылочным типом переадресации, поэтому я думаю, decltype(a)
должно быть int&
а также decltype(b)
должно быть int&&
,
Однако приведенный выше код генерирует следующую ошибку:
main.cpp (13,2): ошибка: нет соответствующей функции для вызова 'f' f (n, std:: move (n));
main.cpp (7,6): примечание: шаблон кандидата игнорируется: выводит конфликтующие типы для параметра 'T' ('int &' vs. 'int')
пустота f (T && a, T && b)
1 ошибка сгенерирована.
Почему это не работает, когда lvalue-ref-arg и rvalue-ref-arg передаются как один и тот же тип forwarding-ref?
Мой компилятор - Clang 4.0.
2 ответа
Ошибка компилятора довольно проста, T
не может быть выведено в int&
а также int
в то же время. Для того, чтобы это работало, вы должны предоставить дополнительный аргумент шаблона:
template<typename T1, typename T2>
void f(T1&& a, T2&& b)
{}
аргументация
Вывод аргумента шаблона следует специальным правилам при работе с переадресацией ссылок (так называемые универсальные ссылки), которые основаны на правилах свертывания ссылок.
- U & & становится U &
- U & && становится U &
- U && & становится U &
- U && && становится U &&
например, если у вас есть шаблон функции:
template<typename T>
void foo(T&&) {}
- Если
foo
вход является lvalue типаU
,T
будет вычтено вU&
, Следование правилам свертывания ссылок над типом аргумента станетU&
, - Если
foo
вход является значением типаU
,T
будет вычтено вU
и, следовательно, тип аргумента будетU&&
,
Теперь, следуя рассуждениям выше, вычет аргумента шаблона будет выводить T
в int&
для вашего первого аргумента, так как вход является lvalue
,
Вычисление аргумента шаблона будет пытаться соответствовать типу второго аргумента, но для второго аргумента, так как input является значением r, следуя приведенным выше правилам. T
будет выведено int
,
В этот момент компилятор вскидывает руки вверх и кричит " чувак" T
вычтенный тип должен совпадать для всех входных аргументов ".
При работе на неповерхностной глубине с пересылкой ссылок(1) важно понимать, как они на самом деле работают, а не рассматривать их как "магию".
Вся "магия" перенаправления ссылок - это правило в стандарте:
Когда тип удержания происходит для типа параметра T &&
где T
является выведенным типом и lvalue типа U
в качестве аргумента используется тип U &
используется для вывода типа вместо U
,
Давайте возьмем этот пример:
template <class T>
void foo(T&& a);
int i = 42;
Что происходит, когда вы звоните foo(42)
: 42
это значение типа int
, Следовательно, T
будет выведено int
и тип a
поэтому будет int &&
Rvalue ссылка на int
,
Что происходит, когда вы звоните foo(i)
: i
является lvalue типа int
, Из-за правил переадресации, вычет будет происходить так, как если бы int &
был тип. T
Поэтому выводится int &
; тип a
следовательно является "int & &&
", которая ссылается на int &
,
Имея это в виду, понятно, почему ваш пример терпит неудачу. Такой же T
должно быть выведено как int &
(потому что n
) и в качестве int
(потому что std::move(n)
). Поэтому, естественно, вычет не удается.
Если вы хотите, чтобы каждый из параметров был ссылкой для пересылки сам по себе, вам нужен отдельный параметр шаблона для каждого:
template<class T, class U>
void f(T&& a, U&& b)
{}
(1) Обратите внимание, что согласно предложению N4164, которое было принято в рабочем документе, предпочтительным термином является пересылка ссылок.