Я использовал ссылки пересылки в своем коде, когда столкнулся с чем-то, чего не понимаю.
Чтобы проиллюстрировать это явление, я написал упрощенную версию своего сценария:
#include <iostream>
template <typename T>
struct Cls
{
static void func(T& b)
{
std::cout << 1 << std::endl;
}
};
template <>
struct Cls<int>
{
static void func(int& b)
{
std::cout << 2 << std::endl;
}
};
template <typename T>
void some_func(T&& a)
{
Cls<T>::func(a);
}
int main()
{
int i{3};
some_func(i);
some_func(3);
}
Насколько я понимаю, универсальные ссылки могут быть либо lvalue
, либо rvalue
.
(A) В моей голове это означает, что первый вызов (some_func(i);
) должен получить доступ к версии функции, где тип a
равен T&
(в данном конкретном случае: int&
), поскольку переданная переменная i
является lvalue
типа int
. Потому что T
— это int
, а поскольку существует специализированная функция Cls<int>::func(int&)
, должно быть совпадение и вывод должен быть 2
.
(B) Второй вызов (some_func(3);
) должен получить доступ к версии, в которой тип a
равен T&&
(в данном случае: int&&
). Я не был полностью уверен, каким будет результат.
Фактический результат, который я получил, был:
> 1
> 2
Обратите внимание, что поведение одинаково даже для непримитивных типов T
.
some_func()
в зависимости от переданных типов ссылок? То есть один для some_func(int& a)
и один для some_func(int&& a)
? Или это на самом деле какой-то новый ссылочный тип внутри той же созданной функции?1
? Почему специализация не используется? Является ли моя интерпретация пункта (А) неверной? В любом случае T
должно быть int
, так как же вместо этого выбрать общую реализацию, если в остальном они идентичны?2
? Учитывая, что первый вызов не смог сопоставить специализацию, когда типы идентичны (int&
), почему он должен иметь возможность сопоставить специализацию при несовпадении (int&
и int&&
)?a
в обоих случаях является xvalue
? То есть, что rvalue
можно интерпретировать как lvalue
, потому что он промежуточно хранится в a
? Это объясняет, почему второй вызов выводит 2
, но не объясняет, почему первый вызов не выводит?Заранее спасибо.
Во втором вызове T
разрешается в int
. Тип a
— int&&
. Cls<T>
затем переходит к Cls<int>
специализации.
Да, программа вызывает два разных экземпляра some_func
, а именно some_func<int&>
и some_func<int>
.
Возможный частичный обман: stackoverflow.com/questions/13725747/…
Небольшое исправление: универсальные ссылки могут быть либо ссылкой на lvalue, либо ссылкой на rvalue.
Обратите внимание, что в вашем коде Cls<T>::func(a);
аргумент a
всегда является ссылкой на lvalue, и поэтому функции, принимающие ссылку на lvalue, сопоставляются в обоих случаях. Чтобы на самом деле переслать ссылку, вам нужно будет использовать std::forward<T>(a)
, и в этом случае код не будет компилироваться, поскольку для аргумента rvalue нет перегрузки.
Тип T
в template <typename T> void some_func(T&& a);
выводится как int&
для первого вызова some_func(i);
, что приводит к созданию экземпляра первичного шаблона Cls<int&>
. А в функции static void func(T& b)
ссылки сворачиваются, в результате чего получается int&
тип аргумента.
При втором вызове some_func(3)
тип T
выводится как int
и, следовательно, выбирается специализация Cls<int>
, и static void func(int& b)
вызывается, поскольку имеется прямое совпадение аргумента.
Вот демо для всех трёх случаев.
При первом вызове
T
разрешается вint&
. Типa
— этоint& &&
, который сворачивается вint&
. Посколькуint& != int
, называется основной шаблон.