pertanyaan saya hari ini cukup sederhana: mengapa kompilator tidak dapat menyimpulkan parameter template dari konstruktor kelas, sebanyak yang dapat dilakukan dari parameter fungsi? Misalnya, mengapa kode berikut tidak valid:
template<typename obj>
class Variable {
obj data;
public: Variable(obj d)
{
data = d;
}
};
int main()
{
int num = 2;
Variable var(num); //would be equivalent to Variable<int> var(num),
return 0; //but actually a compile error
}
Seperti yang saya katakan, saya mengerti bahwa ini tidak valid, jadi pertanyaan saya adalah mengapa tidak? Akankah membiarkan ini menciptakan lubang sintaksis utama? Apakah ada contoh di mana orang tidak menginginkan fungsionalitas ini (di mana menyimpulkan suatu jenis akan menyebabkan masalah)? Saya hanya mencoba untuk memahami logika di balik mengizinkan inferensi template untuk fungsi, namun tidak untuk kelas yang dibuat dengan sesuai.
template<class T> Variable<T> make_Variable(T&& p) {return Variable<T>(std::forward<T>(p));}
Jawaban:
Saya pikir itu tidak valid karena konstruktor tidak selalu satu-satunya titik masuk kelas (saya berbicara tentang copy konstruktor dan operator =). Jadi misalkan Anda menggunakan kelas Anda seperti ini:
Saya tidak yakin apakah akan begitu jelas bagi parser untuk mengetahui jenis template apa MyClass pm;
Tidak yakin apakah yang saya katakan masuk akal tetapi jangan ragu untuk menambahkan komentar, itu pertanyaan yang menarik.
C ++ 17
Diterima bahwa C ++ 17 akan memiliki deduksi tipe dari argumen konstruktor.
Contoh:
Kertas yang diterima .
sumber
MyClass *pm
sini tidak valid karena alasan yang sama bahwa fungsi yang dideklarasikantemplate <typename T> void foo();
tidak dapat dipanggil tanpa spesialisasi eksplisit.Anda tidak dapat melakukan apa yang Anda minta karena alasan orang lain menjawab, tetapi Anda dapat melakukan ini:
yang untuk semua maksud dan tujuan adalah hal yang sama yang Anda minta. Jika Anda menyukai enkapsulasi, Anda dapat menjadikan make_variable sebagai fungsi anggota statis. Itulah yang disebut orang dengan nama konstruktor. Jadi tidak hanya melakukan apa yang Anda inginkan, tetapi juga hampir disebut apa yang Anda inginkan: kompilator menyimpulkan parameter template dari konstruktor (bernama).
NB: kompiler yang masuk akal akan mengoptimalkan objek sementara ketika Anda menulis sesuatu seperti
sumber
auto v = make_variable(instance)
sehingga Anda tidak benar-benar harus menentukan tipenyastatic
anggota ... pikirkan itu sebentar lagi. Bahwa selain: fungsi make bebas memang yang solusi, tapi itu banyak boilerplate berlebihan, bahwa sementara Anda sedang mengetik, Anda hanya tahu Anda tidak harus karena compiler memiliki akses ke semua informasi yang Anda mengulangi. .. dan untungnya C ++ 17 mengkanonisasinya.Di era pencerahan tahun 2016, dengan dua standar baru di bawah ikat pinggang kami sejak pertanyaan ini diajukan dan yang baru sudah dekat, hal penting untuk diketahui adalah bahwa kompiler yang mendukung standar C ++ 17 akan mengkompilasi kode Anda apa adanya .
Pengurangan argumen template untuk template kelas di C ++ 17
Di sini (hasil edit oleh Olzhas Zhumabek dari jawaban yang diterima) adalah makalah yang merinci perubahan yang relevan dengan standar.
Mengatasi masalah dari jawaban lain
Jawaban peringkat teratas saat ini
Jawaban ini menunjukkan bahwa "copy konstruktor dan
operator=
" tidak akan mengetahui spesialisasi template yang benar.Ini tidak masuk akal, karena konstruktor salinan standar dan
operator=
hanya ada untuk jenis template yang dikenal :Di sini, seperti yang saya catat di komentar, tidak ada alasan untuk
MyClass *pm
menjadi deklarasi hukum dengan atau tanpa bentuk inferensi baru:MyClass
bukan tipe (ini template), jadi tidak masuk akal untuk mendeklarasikan pointer dari tipeMyClass
. Berikut salah satu cara yang mungkin untuk memperbaiki contoh:Berikut,
pm
adalah sudah dari jenis yang tepat, dan kesimpulan yang sepele. Selain itu, tidak mungkin untuk mencampur tipe secara tidak sengaja saat memanggil konstruktor salinan:Di sini,
pm
akan menjadi penunjuk ke salinanm
. Di sini,MyClass
sedang dikonstruksi darim
—yang bertipeMyClass<string>
(dan bukan tipe yang tidak adaMyClass
). Dengan demikian, pada titik di manapm
tipe 's disimpulkan, ada adalah informasi yang cukup untuk mengetahui bahwa template-jenism
, dan karena itu template-jenispm
, yangstring
.Selain itu, berikut ini akan selalu memunculkan kesalahan kompilasi :
Ini karena deklarasi konstruktor salinan tidak memiliki template:
Di sini, tipe template argumen copy-constructor cocok dengan tipe template kelas secara keseluruhan; yaitu, saat
MyClass<string>
dibuat instance,MyClass<string>::MyClass(const MyClass<string>&);
dibuat instance-nya, dan ketikaMyClass<int>
dibuat,MyClass<int>::MyClass(const MyClass<int>&);
dibuat instance-nya. Kecuali ditentukan secara eksplisit atau konstruktor templatized dideklarasikan, tidak ada alasan bagi compiler untuk membuat instanceMyClass<int>::MyClass(const MyClass<string>&);
, yang jelas tidak sesuai.Jawabannya oleh Cătălin Pitiș
Pitiș memberikan contoh deduksi
Variable<int>
danVariable<double>
, kemudian menyatakan:Seperti disebutkan dalam contoh sebelumnya,
Variable
itu sendiri bukanlah nama tipe, meskipun fitur baru membuatnya terlihat seperti nama secara sintaksis.Pitiș kemudian menanyakan apa yang akan terjadi jika tidak ada konstruktor yang diberikan yang mengizinkan inferensi yang sesuai. Jawabannya adalah tidak ada inferensi yang diizinkan, karena inferensi dipicu oleh panggilan konstruktor . Tanpa panggilan konstruktor, tidak ada inferensi .
Ini mirip dengan menanyakan versi apa
foo
yang disimpulkan di sini:Jawabannya adalah kode ini ilegal, karena alasan yang disebutkan.
Jawaban MSalter
Ini, sejauh yang saya tahu, satu-satunya jawaban untuk memunculkan kekhawatiran yang sah tentang fitur yang diusulkan.
Contohnya adalah:
Pertanyaan kuncinya adalah, apakah kompilator memilih konstruktor tipe-tersirat di sini atau konstruktor salinan ?
Mencoba kodenya, kita dapat melihat bahwa konstruktor salinan dipilih. Untuk memperluas contoh :
Saya tidak yakin bagaimana proposal dan versi baru standar menentukan ini; tampaknya ditentukan oleh "panduan deduksi," yang merupakan sedikit bahasa standar baru yang saya belum mengerti.
Saya juga tidak yakin mengapa
var4
pemotongan tersebut ilegal; kesalahan kompilator dari g ++ tampaknya menunjukkan bahwa pernyataan tersebut sedang diurai sebagai deklarasi fungsi.sumber
var4
hanya kasus "parse paling menjengkelkan" (tidak terkait dengan pengurangan argumen template). Kami dulu hanya menggunakan tanda kurung ekstra untuk ini, tetapi belakangan ini saya pikir menggunakan kawat gigi untuk menunjukkan konstruksi adalah saran yang biasa.Variable var4(Variable(num));
diperlakukan sebagai deklarasi fungsi? Jika ya, mengapaVariable(num)
spesifikasi parameter yang valid?Masih hilang: Itu membuat kode berikut cukup ambigu:
sumber
Misalkan kompilator mendukung apa yang Anda minta. Maka kode ini valid:
Sekarang, saya memiliki nama jenis yang sama (Variabel) dalam kode untuk dua jenis yang berbeda (Variabel dan Variabel). Dari sudut pandang subjektif saya, ini mempengaruhi keterbacaan kode cukup banyak. Memiliki nama jenis yang sama untuk dua jenis yang berbeda dalam namespace yang sama tampak menyesatkan bagi saya.
Pembaruan nanti: Hal lain yang perlu dipertimbangkan: spesialisasi template sebagian (atau penuh).
Bagaimana jika saya mengkhususkan Variabel dan tidak memberikan konstruktor seperti yang Anda harapkan?
Jadi saya ingin:
Lalu saya punya kode:
Apa yang harus dilakukan kompilator? Gunakan definisi kelas Variabel generik untuk menyimpulkan bahwa itu adalah Variabel, lalu temukan bahwa Variabel tidak menyediakan satu konstruktor parameter?
sumber
C ++ 03 dan standar C ++ 11 tidak mengizinkan pengurangan argumen template dari parameter yang diteruskan ke konsturuktor.
Tapi ada proposal untuk "Pengurangan parameter template untuk konstruktor" sehingga Anda bisa mendapatkan apa yang Anda minta segera. Sunting: memang, fitur ini telah dikonfirmasi untuk C ++ 17.
Lihat: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3602.html dan http://www.open-std.org/jtc1/sc22/wg21/docs/ makalah / 2015 / p0091r0.html
sumber
Banyak kelas tidak bergantung pada parameter konstruktor. Hanya ada beberapa kelas yang hanya memiliki satu konstruktor, dan melakukan parameterisasi berdasarkan jenis konstruktor ini.
Jika Anda benar-benar membutuhkan inferensi template, gunakan fungsi helper:
sumber
Pengurangan tipe terbatas pada fungsi template di C ++ saat ini, tetapi telah lama disadari bahwa pengurangan tipe dalam konteks lain akan sangat berguna. Oleh karena itu C ++ 0x
auto
.Sementara persis apa yang Anda sarankan tidak akan mungkin di C ++ 0x, menunjukkan berikut Anda bisa mendapatkan cukup dekat:
sumber
Anda benar, kompiler dapat dengan mudah menebaknya, tetapi itu tidak dalam standar atau C ++ 0x sejauh yang saya tahu sehingga Anda harus menunggu setidaknya 10 tahun lagi (standar ISO fixed turn around rate) sebelum penyedia compiller menambahkan fitur ini
sumber
Mari kita lihat masalah dengan mengacu pada kelas yang harus dipahami semua orang dengan - std :: vector.
Pertama, penggunaan vektor yang paling umum adalah menggunakan konstruktor yang tidak menggunakan parameter:
Dalam kasus ini, jelas tidak ada kesimpulan yang dapat dilakukan.
Penggunaan umum kedua adalah membuat vektor berukuran sebelumnya:
Di sini, jika inferensi digunakan:
kita mendapatkan vektor int, bukan string, dan mungkin ukurannya tidak!
Terakhir, pertimbangkan konstruktor yang mengambil beberapa parameter - dengan "inferensi":
Parameter mana yang harus digunakan untuk inferensi? Kita memerlukan beberapa cara untuk memberi tahu kompilator bahwa itu harus yang kedua.
Dengan semua masalah ini untuk kelas yang sederhana seperti vektor, mudah untuk melihat mengapa inferensi tidak digunakan.
sumber
Membuat ctor sebagai template Variabel hanya dapat memiliki satu formulir tetapi beragam ctor:
Lihat? Kita tidak bisa memiliki beberapa Variable :: data members.
sumber
Lihat Pemotongan Argumen C ++ Template untuk info lebih lanjut tentang ini.
sumber