Учитывая следующую структуру шаблона:
template<typename T>
struct Foo {
Foo(T&&) {}
};
Это компилируется и T
выводится так int
:
auto f = Foo(2);
Но это не компилируется: https://godbolt.org/z/hAA9TE
int x = 2;
auto f = Foo(x);
/*
<source>:12:15: error: no viable constructor or deduction guide for deduction of template arguments of 'Foo'
auto f = Foo(x);
^
<source>:7:5: note: candidate function [with T = int] not viable: no known conversion from 'int' to 'int &&' for 1st argument
Foo(T&&) {}
^
*/
Однако Foo<int&>(x)
принято.
Но когда я добавляю, казалось бы, избыточное пользовательское руководство по выводам, оно работает:
template<typename T>
Foo(T&&) -> Foo<T>;
Почему не может T
быть выведено как int&
без определенного пользователем руководства по выводу ?
c++
templates
language-lawyer
jtbandes
источник
источник
Foo<T<A>>
Ответы:
Я думаю, что здесь возникает путаница, потому что есть специальное исключение для синтезированных руководств по выводам в отношении пересылки ссылок.
Это правда, что функция-кандидат с целью вычитания аргумента шаблона класса, сгенерированного из конструктора, и функции, сгенерированной из определяемого пользователем руководства по вычету, выглядят абсолютно одинаково, то есть:
но для той, которая сгенерирована из конструктора,
T&&
это простая ссылка rvalue, в то время как в пользовательском случае это ссылка пересылки . Это указано в [temp.deduct.call] / 3 стандарта C ++ 17 (черновик N4659, выделите мой):Поэтому кандидат, синтезированный из конструктора класса, не будет выводить,
T
как если бы он был из ссылки на пересылку (которая моглаT
бы быть ссылкой на lvalue, так чтоT&&
это также ссылка на lvalue), но вместо этого он будет выводить толькоT
как не-ссылку, так чтоT&&
это всегда Rvalue ссылка.источник
Foo<int>(...)
или простоFoo(...)
, что не так с пересылкой ссылок (что может вывестиFoo<int&>
вместо этого.Проблема здесь в том, что, поскольку класс является шаблоном
T
, в конструктореFoo(T&&)
мы не выполняем дедукцию типа; У нас всегда есть ссылка на r-значение. То есть конструктор дляFoo
фактически выглядит так:Foo(2)
работает, потому что2
это prvalue.Foo(x)
не потому, чтоx
является lvalue, который не может связываться сint&&
. Вы могли бы сделать,std::move(x)
чтобы привести его к соответствующему типу ( демо )Foo<int&>(x)
работает просто отлично, потому что конструктор становитсяFoo(int&)
из-за правил свертывания ссылок; Первоначально это то,Foo((int&)&&)
что рушится вFoo(int&)
соответствии со стандартом.Что касается вашего «избыточного» руководства по выводам: изначально для кода существует стандартное руководство по выводам шаблонов, которое в основном действует как вспомогательная функция, например:
Это связано с тем, что стандарт предписывает, чтобы этот (вымышленный) шаблонный метод имел те же параметры шаблона, что и класс (Just
T
), за которыми следуют любые параметры шаблона в качестве конструктора (в данном случае ни одного; конструктор не является шаблонным). Тогда типы параметров функции совпадают с типами в конструкторе. В нашем случае, после создания экземпляраFoo<int>
, конструктор выглядит какFoo(int&&)
rvalue-ссылка другими словами. Следовательно использованиеadd_rvalue_reference_t
выше.Очевидно, это не работает.
Когда вы добавили свое «избыточное» руководство по удержанию:
Вы позволили компилятору различать , что, несмотря на всякого рода ссылку , прикрепленный к
T
в конструкторе (int&
,const int&
или иint&&
т.д.), вы хотели типа , выведенный для класса , чтобы быть без ссылки (толькоT
). Это происходит потому , что мы вдруг будем выполнять вывод типа.Теперь мы генерируем другую (вымышленную) вспомогательную функцию, которая выглядит следующим образом:
(Наши вызовы конструктора перенаправляются во вспомогательную функцию для целей вывода аргументов шаблона класса, поэтому это
Foo(x)
делаетсяMakeFoo(x)
).Это позволяет
U&&
статьint&
иT
стать простоint
источник
x
это значение, которое не может быть привязаноint&&
», но тот, кто не понимает, будет озадачен тем, чтоFoo<int&>(x)
может сработать, но не выяснится автоматически - я думаю, что мы все хотим глубже понять, почему.