Чому 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&без визначеного користувачем посібника з вирахувань?



Здається, це питання стосується таких типів шаблонів, як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&&це також посилання на значення), а натомість буде виводити лише Tяк не посилання, так що T&&це завжди референтне значення.


1
Дякую за чітку та стислу відповідь. Чи знаєте ви, чому для правил переадресації посилань існує виняток для параметрів шаблону класу?
jtbandes

2
@jtbandes Це, здається, було змінено в результаті коментаря національного органу США, див. документ p0512r0 . Я не зміг знайти коментар. Моя здогадка для обґрунтування полягає в тому, що якщо ви пишете конструктору, який бере посилання rvalue, зазвичай ви очікуєте, що він буде працювати так само, якщо ви вказали a Foo<int>(...)або просто Foo(...), що не стосується пересилань посилань (які могли б вивести Foo<int&>замість цього.
walnut

6

Проблема тут полягає в тому, що оскільки клас шаблонується T, у конструкторі Foo(T&&)ми не виконуємо виведення типу; У нас завжди є посилання на значення r. Тобто конструктор для Fooнасправді виглядає так:

Foo(int&&)

Foo(2)працює, тому що 2є первинним.

Foo(x)не тому x, що це значення, яке не може зв'язатись 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&&)реферат-референс іншими словами. Отже, використання 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);

(Наші виклики до конструктора перенаправляються на функцію helper для виведення аргументу шаблону класу, так Foo(x)стає MakeFoo(x)).

Це дозволяє U&&стати int&і Tстати простоint


Другий рівень шаблонування не здається необхідним; яку цінність він надає? Чи можете ви надати посилання на якусь документацію, яка пояснює, чому T&& тут завжди трактується як референтна оцінка?
jtbandes

1
Але якщо T ще не виведено, що означає "будь-який тип, конвертований у T"?
jtbandes

1
Ви швидко запропонуєте вирішити, але чи можете ви зосередитися на поясненні причини, чому це не працює, якщо ви не змінили його якимось чином? Чому це не працює так, як є? " xявляє собою значення, яке не може пов'язуватися з int&&", але той, хто не розуміє, буде здивований, що Foo<int&>(x)міг би працювати, але не з'ясувався автоматично - я думаю, що всі ми хочемо глибше зрозуміти, чому.
Вік

2
@Wyck: Я оновив публікацію, щоб більше зупинитися на тому, чому.
AndyG

3
@Wyck Справжня причина дуже проста: стандарт спеціально вимкнув ідеальну семантику переадресації в синтезованих посібниках вирахування, щоб запобігти сюрпризам.
LF
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.