Является ли это универсальной ссылкой? Означает ли std:: forward смысл?
Рассмотрим этот фрагмент кода, который использует общую идиому создания шаблона функции, создающего экземпляр шаблона класса, специализированного по выведенному типу, как показано в std::make_unique
и std::make_tuple
, например:
template <typename T>
struct foo
{
std::decay_t<T> v_;
foo(T&& v) : v_(std::forward<T>(v)) {}
};
template <typename U>
foo<U> make_foo(U&& v)
{
return { std::forward<U>(v) };
}
В контексте "универсальных ссылок" Скотта Мейерса аргумент
make_foo
является универсальной ссылкой, поскольку ее тип U&&
, где U
равен
выводится. Аргумент конструктору foo
не является универсальной ссылкой
потому что хотя его тип T&&
, T
(вообще говоря) не выведен.
Но в случае, когда конструктор foo
вызывается make_foo
, он
мне кажется, что имеет смысл думать об аргументе конструктору
of foo
как универсальная ссылка, потому что T
был выведен
шаблон функции make_foo
. Будут применяться те же правила обрушения ссылок
так что тип v
в обеих функциях одинаковый. В этом случае оба T
и U
можно сказать, что оно было выведено.
Итак, мой вопрос двоякий:
- Имеет ли смысл думать о аргументе конструктору
foo
как о
универсальная ссылка в ограниченных случаях, в которой T
было выведено
в универсальном справочном контексте вызывающим, как в моем примере?
- В моем примере оба использования
std::forward
разумны?
Ответы
Ответ 1
make_foo
находится в том же самом шаре, что и "справа", но foo
нет. Конструктор foo
в настоящее время принимает только не выводимый T &&
, и пересылка там, вероятно, не имеет значения (но см. Комментарий @nosid). В общем, foo
должен принимать параметр типа, иметь шаблонный конструктор, а функция-maker должна делать разложение:
template <typename T>
struct foo
{
T v_;
template <typename U>
foo(U && u) : v_(std::forward<U>(u)) { }
};
template <typename U>
foo<typename std::decay<U>::type> make_foo(U && u)
{
return foo<typename std::decay<U>::type>(std::forward<U>(u));
}
В С++ 14 функция-создатель становится немного проще писать:
template <typename U>
auto make_foo(U && u)
{ return foo<std::decay_t<U>>(std::forward<U>(u)); }
Как только ваш код будет написан, int a; make_foo(a);
создаст объект типа foo<int &>
. Это внутренне хранит int
, но его конструктор принимает только аргумент int &
. Напротив, make_foo(std::move(a))
создаст foo<int>
.
Таким образом, как вы его написали, аргумент шаблона класса определяет подпись конструктора. (std::forward<T>(v)
все еще имеет смысл в извращенном виде (благодаря @nodis для указания этого), но это определенно не "пересылка".)
Это очень необычно. Как правило, шаблон класса должен определять соответствующий завернутый тип, а конструктор должен принимать все, что может быть использовано для создания завернутого типа, т.е. Конструктор должен быть шаблоном функции.
Ответ 2
Нет формального определения "универсальной ссылки", но я бы определил его как:
A универсальная ссылка - это параметр шаблона функции с типом [template-parameter] &&
с намерением, чтобы параметр шаблона можно было вывести из аргумента функции, и аргумент будет передаваться либо с помощью ссылки lvalue, либо с помощью ссылки rvalue.
Таким образом, по этому определению нет, параметр T&& v
в конструкторе foo
не является универсальной ссылкой.
Однако, вся точка фразы "универсальная ссылка" заключается в том, чтобы представить модель или образец для нас, о которых люди думают при разработке, чтении и понимании кода. И разумно и полезно сказать, что "Когда make_foo
вызывает конструктор foo<U>
, параметр шаблона T
был выведен из аргумента в make_foo
таким образом, который позволяет параметру конструктора T&& v
будь то ссылка на lvalue или ссылочная позиция rvalue, если это необходимо". Это достаточно близко к той же самой концепции, что и в моем случае: "Когда make_foo
вызывает конструктор foo<U>
, параметр конструктора T&& v
по существу является универсальной ссылкой."
Да, оба использования std::forward
будут делать то, что вы намереваетесь здесь, позволяя члену v_
перемещаться из аргумента make_foo
, если это возможно, или копировать в противном случае. Но если make_foo(my_str)
возвращает a foo<std::string&>
, а не a foo<std::string>
, который содержит копию my_str
, довольно удивительно....