Почему C ++ не может вывести T при вызове Foo <T> :: Foo (T &&)?

9

Учитывая следующую структуру шаблона:

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&без определенного пользователем руководства по выводу ?

jtbandes
источник
Этот вопрос, кажется, касается типов шаблонов, таких какFoo<T<A>>
jtbandes

Ответы:

5

Я думаю, что здесь возникает путаница, потому что есть специальное исключение для синтезированных руководств по выводам в отношении пересылки ссылок.

Это правда, что функция-кандидат с целью вычитания аргумента шаблона класса, сгенерированного из конструктора, и функции, сгенерированной из определяемого пользователем руководства по вычету, выглядят абсолютно одинаково, то есть:

template<typename T>
auto f(T&&) -> Foo<T>;

но для той, которая сгенерирована из конструктора, T&&это простая ссылка rvalue, в то время как в пользовательском случае это ссылка пересылки . Это указано в [temp.deduct.call] / 3 стандарта C ++ 17 (черновик N4659, выделите мой):

Ссылка переадресации является ссылкой на Rvalue к CV-неквалифицированным параметра шаблона , который не представляет собой параметр шаблона шаблона класса ( в течение шаблона класса аргумента вычет ([over.match.class.deduct])).

Поэтому кандидат, синтезированный из конструктора класса, не будет выводить, Tкак если бы он был из ссылки на пересылку (которая могла Tбы быть ссылкой на lvalue, так что T&&это также ссылка на lvalue), но вместо этого он будет выводить только Tкак не-ссылку, так что T&&это всегда Rvalue ссылка.

грецкий орех
источник
1
Спасибо за четкий и краткий ответ. Знаете ли вы, почему существует исключение для параметров шаблона класса в правилах для пересылки ссылок?
Jtbandes
2
@jtbandes Это, похоже, было изменено в результате комментария национального органа США, см. документ p0512r0 . Я не мог найти комментарий, хотя. Мое предположение заключается в том, что если вы пишете конструктор, используя ссылку на rvalue, вы обычно ожидаете, что он будет работать одинаково, независимо от того, указали ли вы Foo<int>(...)или просто Foo(...), что не так с пересылкой ссылок (что может вывести Foo<int&>вместо этого.
Walnut
6

Проблема здесь в том, что, поскольку класс является шаблоном 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

AndyG
источник
Второй уровень шаблонизации не кажется необходимым; Какую ценность это обеспечивает? Можете ли вы предоставить ссылку на некоторую документацию, которая разъясняет, почему T && всегда рассматривается здесь как ссылка на значение?
jtbandes
1
Но если T еще не было выведено, что означает «любой тип, преобразуемый в T»?
jtbandes
1
Вы быстро предлагаете обходной путь, но можете ли вы сосредоточиться на объяснении по той причине, почему оно не работает, если вы не измените его каким-либо образом? Почему это не работает как есть? « xэто значение, которое не может быть привязано int&&», но тот, кто не понимает, будет озадачен тем, что Foo<int&>(x)может сработать, но не выяснится автоматически - я думаю, что мы все хотим глубже понять, почему.
Вик
2
@Wyck: я обновил пост, чтобы больше сосредоточиться на том, почему.
AndyG
3
@Wyck Настоящая причина очень проста: стандарт специально отключил идеальную семантику пересылки в синтезированных руководствах по выводам, чтобы избежать неожиданностей.
LF