Mengapa {} sebagai argumen fungsi tidak mengarah pada ambiguitas?

20

Pertimbangkan kode ini:

#include <vector>
#include <iostream>

enum class A
{
  X, Y
};

struct Test
{
  Test(const std::vector<double>&, const std::vector<int>& = {}, A = A::X)
  { std::cout << "vector overload" << std::endl; }

  Test(const std::vector<double>&, int, A = A::X)
  { std::cout << "int overload" << std::endl; }
};

int main()
{
  std::vector<double> v;
  Test t1(v);
  Test t2(v, {}, A::X);
}

https://godbolt.org/z/Gc_w8i

Ini mencetak:

vector overload
int overload

Mengapa ini tidak menghasilkan kesalahan kompilasi karena resolusi kelebihan yang ambigu? Jika konstruktor kedua dihapus, kami mendapatkan vector overloaddua kali. Bagaimana / dengan metrik manakah intkecocokan yang pasti lebih baik {}daripada std::vector<int>?

Tanda tangan konstruktor pasti dapat dipangkas lebih lanjut, tapi saya baru saja diakali oleh sepotong kode yang setara dan ingin memastikan tidak ada yang penting yang hilang untuk pertanyaan ini.

Max Langhof
sumber
Jika saya ingat dengan benar {}sebagai blok kode, berikan 0 ke variabel - contoh: const char x = {}; diatur ke 0 (null char), sama untuk int dll.
Seti
2
@ Seti Itulah yang {}efektif dalam kasus-kasus khusus tertentu, tetapi umumnya tidak benar (untuk permulaan, std::vector<int> x = {};bekerja, std::vector <int> x = 0;tidak). Ini tidak sesederhana " {}menetapkan nol".
Max Langhof
Benar, ini tidak begitu sederhana, tetapi masih memberikan nol - saya pikir ini beaviour cukup membingungkan dan tidak boleh digunakan benar
Seti
2
@Seti struct A { int x = 5; }; A a = {};tidak menetapkan nol dalam arti apa pun, itu membangun sebuah Adengan a.x = 5. Ini tidak seperti A a = { 0 };, yang menginisialisasi a.xke 0. Nol tidak melekat pada {}, itu melekat pada bagaimana setiap jenis dibangun secara default atau nilai-diinisialisasi. Lihat di sini , di sini dan di sini .
Max Langhof
Saya masih berpikir bahwa nilai bawaan dibangun membingungkan (mengharuskan Anda untuk memeriksa perilaku atau menyimpan banyak pengetahuan sepanjang waktu)
Seti

Jawaban:

12

Ada di [over.ics.list] , beri penekanan pada saya

6 Sebaliknya, jika parameternya adalah kelas X non-agregat dan resolusi kelebihan per [over.match.list] memilih konstruktor C terbaik dari X untuk melakukan inisialisasi objek tipe X dari daftar penginisialisasi argumen:

  • Jika C bukan konstruktor daftar penginisialisasi dan daftar penginisialisasi memiliki elemen tunggal dari tipe cv U, di mana U adalah X atau kelas yang berasal dari X, urutan konversi tersirat memiliki peringkat Pencocokan Tepat jika U adalah X, atau peringkat Konversi jika U berasal dari X.

  • Jika tidak, urutan konversi implisit adalah urutan konversi yang ditentukan pengguna dengan urutan konversi standar kedua konversi identitas.

9 Sebaliknya, jika tipe parameter bukan kelas:

  • [...]

  • jika daftar penginisialisasi tidak memiliki elemen, urutan konversi tersirat adalah konversi identitas. [Contoh:

    void f(int);
    f( { } ); // OK: identity conversion

    contoh akhir]

Ini std::vectordiinisialisasi oleh konstruktor dan peluru dalam huruf tebal dianggap sebagai konversi yang didefinisikan pengguna. Sementara itu, bagi seorang int, ini adalah konversi identitas, sehingga mengalahkan pangkat c'tor pertama.

StoryTeller - Unslander Monica
sumber
Ya, sepertinya akurat.
Columbo
Menarik untuk melihat bahwa situasi ini secara eksplisit dipertimbangkan dalam standar. Saya akan benar-benar berharap itu menjadi ambigu (dan sepertinya bisa dengan mudah ditentukan seperti itu). Saya tidak bisa mengikuti alasan dalam kalimat terakhir Anda - 0bertipe inttetapi tidak mengetik std::vector<int>, bagaimana itu "seolah-olah" sesuai dengan sifat "tidak diketik" {}?
Max Langhof
@ MaxLanghof - Cara lain untuk melihatnya adalah untuk tipe non-kelas, itu bukan konversi yang ditentukan pengguna dengan rentang apa pun. Sebagai gantinya itu adalah penginisialisasi langsung untuk nilai default. Oleh karena itu identitas dalam hal ini.
StoryTeller - Unslander Monica
Bagian itu jelas. Saya terkejut dengan perlunya konversi yang ditentukan pengguna untuk std::vector<int>. Seperti yang Anda katakan, saya perkirakan "tipe parameter akhirnya memutuskan apa tipe argumennya", dan {}"tipe" (bisa dikatakan) std::vector<int>tidak perlu konversi (non-identitas) untuk menginisialisasi a std::vector<int>. Standar itu jelas mengatakan ya, jadi begitu, tapi itu tidak masuk akal bagi saya. (Pikiran Anda, saya tidak berpendapat bahwa Anda atau standar salah, hanya mencoba untuk mendamaikan ini dengan model mental saya.)
Max Langhof
Ok, suntingan itu bukan resolusi yang saya harapkan, tapi cukup adil. : D Terima kasih atas waktu Anda!
Max Langhof