Diberikan kerangka templat berikut:
template<typename T>
struct Foo {
Foo(T&&) {}
};
Ini mengkompilasi, dan T
disimpulkan 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 T
dapat dideduksi karena int&
tanpa panduan deduksi yang ditentukan pengguna?
c++
templates
language-lawyer
jtbandes
sumber
sumber
Foo<T<A>>
Jawaban:
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:
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):Oleh karena itu kandidat yang disintesis dari konstruktor kelas tidak akan menyimpulkan
T
seolah-olah dari referensi penerusan (yang dapat disimpulkanT
sebagai referensi nilai, sehinggaT&&
juga merupakan referensi nilai), tetapi sebaliknya hanya akan menyimpulkanT
sebagai referensi, sehinggaT&&
selalu referensi nilai.sumber
Foo<int>(...)
atau adilFoo(...)
, yang tidak demikian halnya dengan referensi penerusan (yang dapat menyimpulkanFoo<int&>
sebagai gantinya.Masalahnya di sini adalah, karena kelas digunakan
T
, pada konstruktorFoo(T&&)
kita tidak melakukan deduksi tipe; Kami selalu memiliki referensi r-value. Artinya, konstruktor untukFoo
benar - benar terlihat seperti ini:Foo(2)
berfungsi karena2
merupakan nilai awal.Foo(x)
tidak karenax
merupakan nilai yang tidak dapat mengikatint&&
. Anda dapat melakukannyastd::move(x)
untuk melemparkannya ke jenis yang sesuai ( demo )Foo<int&>(x)
berfungsi dengan baik karena konstruktor menjadiFoo(int&)
karena aturan runtuh referensi; awalnya iniFoo((int&)&&)
yang runtuhFoo(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:
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 instantiatingFoo<int>
, konstruktornya terlihat sepertiFoo(int&&)
, referensi-nilai dengan kata lain. Karenanya penggunaan diadd_rvalue_reference_t
atas.Jelas ini tidak berhasil.
Saat Anda menambahkan panduan deduksi "redundan" Anda:
Anda diperbolehkan compiler untuk membedakan bahwa, meskipun setiap jenis referensi yang melekat
T
dalam konstruktor (int&
,const int&
, atauint&&
dll), Anda bermaksud jenis disimpulkan untuk kelas menjadi tanpa referensi (hanyaT
). Ini karena kita tiba - tiba melakukan inferensi tipe.Sekarang kita menghasilkan fungsi pembantu (fiksi) lain yang terlihat seperti ini:
(Panggilan kami untuk konstruktor diarahkan ke fungsi pembantu untuk keperluan kelas template argumen deduksi, sehingga
Foo(x)
menjadiMakeFoo(x)
).Ini memungkinkan
U&&
untuk menjadiint&
danT
menjadi sederhanaint
sumber
x
Adalah nilai yang tidak bisa mengikatint&&
" tetapi seseorang yang tidak mengerti akan bingung yangFoo<int&>(x)
bisa bekerja tetapi tidak secara otomatis tahu - saya pikir kita semua ingin lebih memahami mengapa.