Почему C ++ не может вывести T при вызове Foo <T>:: Foo (T &&)? - PullRequest
9 голосов
/ 01 марта 2020

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

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

Ответы [ 2 ]

6 голосов
/ 01 марта 2020

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

5 голосов
/ 01 марта 2020

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

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

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

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

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

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

...