Проблема здесь в том, что, поскольку класс является шаблоном T, в конструкторе Foo(T&&)мы не выполняем дедукцию типа; У нас всегда есть ссылка на r-значение. То есть конструктор для Fooфактически выглядит так:
Foo(int&&)
Foo(2)работает, потому что 2это prvalue.
Foo(x)не потому, что xявляется lvalue, который не может связываться с int&&. Вы могли бы сделать, std::move(x)чтобы привести его к соответствующему типу ( демо )
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), за которыми следуют любые параметры шаблона в качестве конструктора (в данном случае ни одного; конструктор не является шаблонным). Тогда типы параметров функции совпадают с типами в конструкторе. В нашем случае, после создания экземпляра Foo<int>, конструктор выглядит как Foo(int&&)rvalue-ссылка другими словами. Следовательно использование add_rvalue_reference_tвыше.
Очевидно, это не работает.
Когда вы добавили свое «избыточное» руководство по удержанию:
template<typename T>
Foo(T&&) -> Foo<T>;
Вы позволили компилятору различать , что, несмотря на всякого рода ссылку , прикрепленный к Tв конструкторе ( int&, const int&или и int&&т.д.), вы хотели типа , выведенный для класса , чтобы быть без ссылки (только 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