Является ли это универсальной ссылкой? Означает ли 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, довольно удивительно....