Проблема здесь в том, что, поскольку класс настроен на T
, в конструкторе Foo(T&&)
мы не выполняем вывод типа; У нас всегда есть ссылка на r-значение. То есть конструктор для Foo
на самом деле выглядит следующим образом:
Foo(int&&)
Foo(2)
работает, потому что 2
является prvalue.
Foo(x)
не потому, что x
является lvalue, который не может связываться с int&&
. Вы можете сделать std::move(x)
, чтобы привести его к соответствующему типу ( demo )
Foo<int&>(x)
работает просто отлично, потому что конструктор становится Foo(int&)
из-за правил свертывания ссылок; Первоначально это Foo((int&)&&)
, который падает до Foo(int&)
в соответствии со стандартом.
Что касается вашего "избыточного" руководства по выводам: изначально для кода существует стандартное руководство по выводам шаблонов, которое в основном действует как вспомогательная функция, такая как Итак:
template<typename T>
struct Foo {
Foo(T&&) {}
};
template<typename T>
Foo<T> MakeFoo(std::add_rvalue_reference_t<T> value)
{
return Foo<T>(std::move(value));
}
//...
auto f = MakeFoo(x);
Это потому, что стандарт диктует, что этот (вымышленный) метод шаблона имеет те же параметры шаблона, что и класс (Just T
), за которыми следуют любые параметры шаблона в качестве конструктора (в этом нет ни одного) case; конструктор не является шаблоном). Тогда типы параметров функции совпадают с типами в конструкторе. В нашем случае, после создания экземпляра Foo<int>
, конструктор выглядит как Foo(int&&)
, другими словами, rvalue-ссылка. Следовательно, использование add_rvalue_reference_t
выше.
Очевидно, это не сработает.
Когда вы добавили свое «избыточное» руководство по выводам:
template<typename T>
Foo(T&&) -> Foo<T>;
Вы разрешили компилятор, чтобы различать guish, что, несмотря на любые ссылки на T
в конструкторе (int&
, const int&
или int&&
et c.), вы предполагали тип, выведенный для класс должен быть без ссылки (просто T
). Это потому, что мы неожиданно выполняем вывод типа.
Теперь мы генерируем другую (вымышленную) вспомогательную функцию, которая выглядит следующим образом:
template<class U>
Foo<U> MakeFoo(U&& u)
{
return Foo<U>(std::forward<U>(u));
}
// ...
auto f = MakeFoo(x);
(Наши вызовы конструктор перенаправляется в вспомогательную функцию для целей вывода аргументов шаблона класса, поэтому Foo(x)
становится MakeFoo(x)
).
Это позволяет U&&
стать int&
и T
стать простым int