Mengapa C ++ tidak dapat menyimpulkan T dalam panggilan ke Foo <T> :: Foo (T &&)?

9

Diberikan kerangka templat berikut:

template<typename T>
struct Foo {
    Foo(T&&) {}
};

Ini mengkompilasi, dan Tdisimpulkan menjadi int:

auto f = Foo(2);

Tetapi ini tidak dapat dikompilasi: 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&&) {}
    ^
*/

Namun, Foo<int&>(x)diterima.

Tetapi ketika saya menambahkan panduan deduksi yang tampaknya redundan oleh pengguna, ia berfungsi:

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

Mengapa tidak Tdapat dideduksi karena int&tanpa panduan deduksi yang ditentukan pengguna?

jtbandes
sumber
Pertanyaan itu tampaknya tentang jenis template sepertiFoo<T<A>>
jtbandes

Jawaban:

5

Saya pikir kebingungan di sini muncul karena ada pengecualian khusus untuk panduan deduksi disintesis mengenai meneruskan referensi.

Memang benar bahwa fungsi kandidat untuk tujuan pengurangan argumen templat kelas yang dihasilkan dari konstruktor dan yang dihasilkan dari panduan deduksi yang ditentukan pengguna terlihat persis sama, yaitu:

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

tetapi untuk yang dihasilkan dari konstruktor, T&&adalah referensi nilai sederhana, sementara itu adalah referensi penerusan dalam kasus yang ditentukan pengguna. Ini ditentukan oleh [temp.deduct.call] / 3 dari standar C ++ 17 (konsep N4659, tekankan milik saya):

Sebuah referensi forwarding adalah sebuah referensi nilai p untuk template parameter cv-wajar tanpa pengecualian yang tidak mewakili parameter template template kelas (selama kelas template argumen deduksi ([over.match.class.deduct])).

Oleh karena itu kandidat yang disintesis dari konstruktor kelas tidak akan menyimpulkan Tseolah-olah dari referensi penerusan (yang dapat disimpulkan Tsebagai referensi nilai, sehingga T&&juga merupakan referensi nilai), tetapi sebaliknya hanya akan menyimpulkan Tsebagai referensi, sehingga T&&selalu referensi nilai.

kenari
sumber
1
Terima kasih atas jawaban yang jelas dan ringkas. Apakah Anda tahu mengapa ada pengecualian untuk parameter templat kelas dalam aturan untuk meneruskan referensi?
jtbandes
2
@jtbandes Ini sepertinya telah diubah sebagai hasil dari komentar oleh badan nasional AS, lihat paper p0512r0 . Saya tidak dapat menemukan komentar. Dugaan saya untuk alasannya adalah bahwa jika Anda menulis sebuah konstruktor yang mengambil referensi nilai, Anda biasanya mengharapkannya bekerja dengan cara yang sama apakah Anda menentukan Foo<int>(...)atau adil Foo(...), yang tidak demikian halnya dengan referensi penerusan (yang dapat menyimpulkan Foo<int&>sebagai gantinya.
walnut
6

Masalahnya di sini adalah, karena kelas digunakan T, pada konstruktor Foo(T&&)kita tidak melakukan deduksi tipe; Kami selalu memiliki referensi r-value. Artinya, konstruktor untuk Foobenar - benar terlihat seperti ini:

Foo(int&&)

Foo(2)berfungsi karena 2merupakan nilai awal.

Foo(x)tidak karena xmerupakan nilai yang tidak dapat mengikat int&&. Anda dapat melakukannya std::move(x)untuk melemparkannya ke jenis yang sesuai ( demo )

Foo<int&>(x)berfungsi dengan baik karena konstruktor menjadi Foo(int&)karena aturan runtuh referensi; awalnya ini Foo((int&)&&)yang runtuh Foo(int&)sesuai standar.

Sehubungan dengan panduan pengurangan "redundant" Anda: Awalnya ada panduan pengurangan template default untuk kode yang pada dasarnya bertindak seperti fungsi pembantu seperti:

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);

Ini karena standar menyatakan bahwa metode templat (fiksi) ini memiliki parameter templat yang sama dengan kelas (Hanya T) diikuti oleh parameter templat apa pun sebagai konstruktor (tidak ada dalam kasus ini; konstruktor tidak templated). Kemudian, jenis parameter fungsi sama dengan yang ada di konstruktor. Dalam kasus kami, setelah instantiating Foo<int>, konstruktornya terlihat seperti Foo(int&&), referensi-nilai dengan kata lain. Karenanya penggunaan di add_rvalue_reference_tatas.

Jelas ini tidak berhasil.

Saat Anda menambahkan panduan deduksi "redundan" Anda:

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

Anda diperbolehkan compiler untuk membedakan bahwa, meskipun setiap jenis referensi yang melekat Tdalam konstruktor ( int&, const int&, atau int&&dll), Anda bermaksud jenis disimpulkan untuk kelas menjadi tanpa referensi (hanya T). Ini karena kita tiba - tiba melakukan inferensi tipe.

Sekarang kita menghasilkan fungsi pembantu (fiksi) lain yang terlihat seperti ini:

template<class U>
Foo<U> MakeFoo(U&& u)
{
   return Foo<U>(std::forward<U>(u));
}

// ...
auto f = MakeFoo(x);

(Panggilan kami untuk konstruktor diarahkan ke fungsi pembantu untuk keperluan kelas template argumen deduksi, sehingga Foo(x)menjadi MakeFoo(x)).

Ini memungkinkan U&&untuk menjadi int&dan Tmenjadi sederhanaint

AndyG
sumber
Level kedua dari templating tampaknya tidak perlu; nilai apa yang diberikannya? Bisakah Anda memberikan tautan ke beberapa dokumentasi yang menjelaskan mengapa T&& selalu diperlakukan sebagai referensi nilai di sini?
jtbandes
1
Tetapi jika T belum disimpulkan, apa artinya "semua jenis konversi menjadi T"?
jtbandes
1
Anda cepat menawarkan solusi, tetapi dapatkah Anda lebih fokus pada penjelasan karena alasannya tidak berfungsi kecuali jika Anda memodifikasinya? Mengapa itu tidak berfungsi sebagaimana mestinya? " xAdalah nilai yang tidak bisa mengikat int&&" tetapi seseorang yang tidak mengerti akan bingung yang Foo<int&>(x)bisa bekerja tetapi tidak secara otomatis tahu - saya pikir kita semua ingin lebih memahami mengapa.
Wyck
2
@Wyck: Saya telah memperbarui posting untuk lebih fokus pada alasannya.
AndyG
3
@Wyck Alasan sebenarnya sangat sederhana: standar khusus mematikan semantik penerusan sempurna dalam panduan deduksi disintesis untuk mencegah kejutan.
LF