ทำไม {} เนื่องจากอาร์กิวเมนต์ของฟังก์ชันไม่นำไปสู่ความกำกวม?


20

พิจารณารหัสนี้:

#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

ภาพพิมพ์นี้:

vector overload
int overload

เหตุใดสิ่งนี้จึงไม่เกิดข้อผิดพลาดในการคอมไพล์เนื่องจากการแก้ปัญหาการโอเวอร์โหลดที่ไม่ชัดเจน หากตัวสร้างที่สองถูกลบเราจะได้รับvector overloadสองครั้ง วิธี / โดยสิ่งที่ตัวชี้วัดคือintมั่นที่ดีขึ้นตรงกับ{}กว่าstd::vector<int>?

ลายเซ็นตัวสร้างสามารถตัดต่อได้ แต่ฉันเพิ่งถูกหลอกด้วยโค้ดที่เทียบเท่ากันและต้องการให้แน่ใจว่าไม่มีอะไรสำคัญที่หายไปสำหรับคำถามนี้


ถ้าฉันจำ corretly {}เป็นบล็อกของรหัสกำหนด 0 ให้กับตัวแปร - ตัวอย่าง: const char x = {}; ถูกตั้งค่าเป็น 0 (null ถ่าน) เหมือนกันสำหรับ int ฯลฯ
Seti

2
@Seti นั่นคือสิ่งที่{}มีประสิทธิภาพในบางกรณีพิเศษ แต่โดยทั่วไปจะไม่ถูกต้อง (สำหรับ starters, std::vector<int> x = {};works, std::vector <int> x = 0;not) มันไม่ง่ายเหมือน " {}กำหนดศูนย์"
Max Langhof

ใช่มันไม่ง่ายนัก แต่มันก็ยังคงกำหนดศูนย์ - คิดว่าฉันคิดว่าพฤติกรรมนี้ค่อนข้างสับสนและไม่ควรใช้จริงๆ
Seti

2
@Seti struct A { int x = 5; }; A a = {};ไม่ได้กำหนดเป็นศูนย์ในความรู้สึกใด ๆ ก็สร้างกับA a.x = 5สิ่งนี้ไม่เหมือนA a = { 0 };ซึ่งจะเริ่มต้นa.xเป็น 0 ศูนย์ไม่ได้มีอยู่ในตัว{}มันเป็นสิ่งที่สืบทอดว่าแต่ละประเภทนั้นถูกสร้างขึ้นเป็นค่าเริ่มต้นหรือกำหนดค่าเริ่มต้นได้อย่างไร ดูที่นี่ , ที่นี่และที่นี่
Max Langhof

ฉันยังคิดว่าค่าเริ่มต้นที่สร้างขึ้นมีความสับสน (คุณต้องตรวจสอบพฤติกรรมหรือให้ความรู้ตลอดเวลา)
Seti

คำตอบ:


12

มันอยู่ใน[over.ics.list]ซึ่งเป็นการเน้นย้ำ

6มิฉะนั้นหากพารามิเตอร์เป็นคลาส X ที่ไม่รวมกันและการแก้ปัญหาการโอเวอร์โหลดต่อ [over.match.list] เลือกคอนสตรัคเตอร์ที่ดีที่สุดเพียงตัวเดียว C ของ X เพื่อดำเนินการเริ่มต้นของวัตถุประเภท X จากรายการอาร์กิวเมนต์เริ่มต้น:

  • ถ้า C คือ ไม่ใช่ตัวสร้างรายการเริ่มต้นและรายการตัวเริ่มต้นมีองค์ประกอบเดียวของประเภท cv U โดยที่ U คือ X หรือคลาสที่ได้มาจาก X ลำดับการแปลงโดยนัยมีอันดับที่ตรงกันแบบตรงถ้า U เป็น X หรืออันดับการแปลงถ้า คุณได้มาจาก X

  • มิฉะนั้นลำดับการแปลงโดยนัยคือลำดับการแปลงที่ผู้ใช้กำหนดด้วยลำดับการแปลงมาตรฐานที่สองคือการแปลงข้อมูลประจำตัว

9มิฉะนั้นถ้าประเภทพารามิเตอร์ไม่ใช่คลาส:

  • [ ... ]

  • ถ้ารายการ initializer ไม่มีองค์ประกอบลำดับการแปลงโดยนัยคือการแปลงข้อมูลประจำตัว [ตัวอย่าง:

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

    ตัวอย่างท้าย]

การstd::vectorเริ่มต้นโดยคอนสตรัคและกระสุนในตัวหนาเห็นว่ามันเป็นคอนดิชั่นที่ผู้ใช้กำหนด ในขณะเดียวกันสำหรับintนี่คือการแปลงอัตลักษณ์ดังนั้นมันสำคัญกว่าอันดับแรกของ c'tor


ใช่ดูเหมือนถูกต้อง
Columbo

น่าสนใจที่จะเห็นว่าสถานการณ์นี้ได้รับการพิจารณาอย่างชัดเจนในมาตรฐาน ฉันคาดหวังว่ามันจะคลุมเครือจริง ๆ (และดูเหมือนว่ามันจะถูกระบุอย่างง่ายดาย) ฉันไม่สามารถทำตามเหตุผลในประโยคสุดท้ายของคุณ - 0มีประเภทintแต่ไม่ได้พิมพ์std::vector<int>เป็นอย่างไร "ราวกับว่า" wrt กับลักษณะ "untyped" ของ{}?
Max Langhof

@MaxLanghof - วิธีอื่นในการดูคือสำหรับประเภทที่ไม่ใช่คลาสไม่ใช่การแปลงที่ผู้ใช้กำหนดโดยส่วนขยายใด ๆ แทนมันเป็นตัวเริ่มต้นโดยตรงสำหรับค่าเริ่มต้น ดังนั้นตัวตนในกรณีนี้
StoryTeller - Unslander Monica

ส่วนนั้นชัดเจน std::vector<int>ฉันประหลาดใจโดยความจำเป็นในการแปลงที่ผู้ใช้กำหนดไป ในขณะที่คุณกล่าวว่าฉันคาดว่า "ประเภทของพารามิเตอร์ปลายขึ้นตัดสินใจเลือกสิ่งที่ประเภทของการโต้แย้งว่า" และ{}"ประเภท" (เพื่อจะพูด) std::vector<int>ไม่ควรต้อง (ไม่ใช่ตัวตน) std::vector<int>การแปลงเริ่มต้น มาตรฐานเห็นได้ชัดว่ามันเป็นอย่างนั้น แต่มันก็ไม่สมเหตุสมผลสำหรับฉัน (โปรดทราบว่าฉันไม่ได้โต้เถียงว่าคุณหรือมาตรฐานผิดเพียงพยายามคืนดีกับแบบจำลองทางจิตของฉัน)
Max Langhof

ตกลงการแก้ไขนั้นไม่ใช่ความละเอียดที่ฉันหวังไว้ แต่ก็ยุติธรรมพอ : D ขอบคุณสำหรับเวลาของคุณ!
Max Langhof
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.