Почему это не работает, когда 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&&) {}
  1. Если foo вход является lvalue типа U, T будет вычтено в U&, Следование правилам свертывания ссылок над типом аргумента станет U&,
  2. Если 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, которое было принято в рабочем документе, предпочтительным термином является пересылка ссылок.

Другие вопросы по тегам