ทำไมคลาสของฉันจึงไม่ใช่ค่าเริ่มต้นที่สามารถสร้างได้?


28

ฉันมีชั้นเรียนเหล่านั้น:

#include <type_traits>

template <typename T>
class A {
public:
    static_assert(std::is_default_constructible_v<T>);

};

struct B {
   struct C {
      int i = 0;
   };

    A<C> a_m;
};

int main() {
    A<B::C> a;
}

เมื่อทำการคอมไพล์a_mไม่ใช่ค่าเริ่มต้นที่สามารถสร้างได้ แต่aเป็น

เมื่อเปลี่ยนCเป็น:

struct C {
      int i;
   };

ทุกอย่างปกติดี.

ทดสอบกับเสียงดังกราว 9.0.0


3
GCC 8.3 - ตกลง GCC 9.1 / 9.2 - ล้มเหลว
Evg

2
ด้วยการC() {}ทำงานเช่นกัน
Evg

3
นี่เป็นกลิ่นรถสำหรับฉัน ไม่มีการจับคู่ที่ชัดเจนใน Bugzilla ทันที
การแข่งขัน Lightness ใน Orbit

2
ที่น่าสนใจ: การล้มเหลวstatic_assertในAแต่ถ้าคุณแทนการสร้างTภายในของA(เช่นใส่สมาชิกที่T t;นั่น) มันทำงานได้ดี ความไม่ลงรอยกันระหว่างสิ่งที่ลักษณะนิสัยกำลังบอกคุณกับสิ่งที่เป็นไปได้จริง ๆ ...
35726

2
@ Nicolas True แต่นั่นเป็นเพราะกรณีบางกรณีไม่มีสิ่งใดที่นี่ (โดยเฉพาะอย่างยิ่งในประโยคเดียวกับ cppreference กล่าวว่าconst int x;ไม่ถูกต้องหากไม่มี initialiser หมดจดเนื่องจากconstพฤติกรรมการเริ่มต้นและประเภทเริ่มต้นในตัวและบางอย่าง ประวัติศาสตร์)
การแข่งขัน Lightness ใน Orbit

คำตอบ:


9

สิ่งนี้ไม่ได้รับอนุญาตทั้งโดยข้อความของมาตรฐานและการใช้งานที่สำคัญหลายประการตามที่ระบุไว้ในความคิดเห็น แต่ด้วยเหตุผลที่ไม่เกี่ยวข้องอย่างสมบูรณ์

ประการแรกเหตุผล "โดยหนังสือ": จุดอินสแตนซ์ของA<C>คือตามมาตรฐาน ทันทีก่อนที่จะนิยามBและจุดอินสแตนซ์ของstd::is_default_constructible<C>ทันทีก่อนหน้านั้น:

สำหรับความเชี่ยวชาญเทมเพลตคลาส [... ] ถ้าความเชี่ยวชาญนั้นถูกทำให้เป็นอินสแตนซ์โดยปริยายเพราะมันถูกอ้างอิงจากภายในความเชี่ยวชาญของเทมเพลตอื่นถ้าบริบทที่การอ้างอิงความเชี่ยวชาญนั้นขึ้นอยู่กับพารามิเตอร์เทมเพลตและหากความเชี่ยวชาญนั้นไม่ได้สร้างอินสแตนซ์ ในการสร้างอินสแตนซ์ของเทมเพลตที่ล้อมรอบจุดของการสร้างอินสแตนซ์นั้นจะอยู่ด้านหน้าก่อนที่จะเริ่มการสร้างอินสแตนซ์ของเทมเพลตที่ล้อมรอบ มิฉะนั้นจุดของการสร้างอินสแตนซ์สำหรับความเชี่ยวชาญพิเศษนั้นจะนำหน้าการประกาศขอบเขตเนมสเปซหรือคำจำกัดความที่อ้างถึงความเชี่ยวชาญทันที

เนื่องจากจุดCนั้นไม่สมบูรณ์อย่างชัดเจนพฤติกรรมของการสร้างอินสแตนซ์std::is_default_constructible<C>จึงไม่ได้กำหนดไว้ อย่างไรก็ตามดูปัญหาหลัก 287ซึ่งจะเปลี่ยนกฎนี้


ในความเป็นจริงสิ่งนี้เกี่ยวข้องกับ NSDMI

  • NSDMI นั้นแปลกเพราะพวกมันได้รับการแยกวิเคราะห์ล่าช้าหรือในสำนวนมาตรฐานพวกเขาเป็น "บริบทที่สมบูรณ์แบบ"
  • ดังนั้นที่= 0สามารถในหลักการหมายถึงสิ่งที่อยู่ในที่ยังไม่ได้ประกาศเพื่อให้การดำเนินการไม่สามารถจริงๆพยายามที่จะแยกมันจนกว่าจะได้จบด้วยBB
  • การทำให้คลาสเสร็จสมบูรณ์จำเป็นต้องมีการประกาศโดยนัยของฟังก์ชันสมาชิกพิเศษโดยเฉพาะคอนสตรัคเตอร์เริ่มต้นเนื่องจากCไม่มีคอนสตรัคเตอร์ที่ประกาศ
  • บางส่วนของการประกาศนั้น (constexpr-ness, noexcept-ness) ขึ้นอยู่กับคุณสมบัติของ NSDMI
  • ดังนั้นหากคอมไพเลอร์ไม่สามารถแยกวิเคราะห์ NSDMI มันจะไม่สามารถเรียนได้
  • เป็นผลให้เมื่อถึงจุดที่มัน instantiates A<C>มันคิดว่าCมันไม่สมบูรณ์

พื้นที่ทั้งหมดที่เกี่ยวข้องกับภูมิภาคที่มีการแยกวิเคราะห์ล่าช้านี้ไม่ได้รับการเน้นยิบอย่างเด็ดขาด อาจใช้เวลาสักครู่ก่อนทำความสะอาด


0

พฤติกรรมที่ไม่ได้กำหนดเป็น:

หากการสร้างอินสแตนซ์ของเทมเพลตข้างต้นขึ้นอยู่กับประเภทที่ไม่สมบูรณ์ทั้งทางตรงและทางอ้อมและการสร้างอินสแตนซ์นั้นอาจให้ผลลัพธ์ที่แตกต่างหากประเภทนั้นเสร็จสมบูรณ์ตามสมมุติฐานพฤติกรรมจะไม่ได้กำหนดไว้


7
เหตุใด C จึงไม่สมบูรณ์
ระหว่าง

1
@interjay Cเสร็จสมบูรณ์ แต่Bไม่ใช่ และขึ้นอยู่บนทางอ้อมB::C B
Evg

1
@Evg ข้อความ "ขึ้นอยู่กับโดยตรงหรือโดยอ้อม" ปรากฏเฉพาะใน cppreference.com มาตรฐานเพิ่งบอกว่าต้องพิมพ์ T ให้เสร็จสมบูรณ์
ระหว่าง


2
@interjay ฉันเขียนถ้อยคำนี้เป็นส่วนใหญ่ สิ่งที่เราพยายามจะพูดคือ 1) หากคุณยกตัวอย่างลักษณะที่อาจก่อให้เกิดการละเมิด ODR ในภายหลังเมื่อประเภทที่ไม่สมบูรณ์บางอย่างเสร็จสมบูรณ์นั่นไม่ได้กำหนดไว้; และ 2) มันไม่ได้กำหนดแม้ว่าคุณจะทำไม่ได้จริงทำให้เกิดการละเมิด ODR ในโปรแกรมของคุณเพื่อให้การใช้งานห้องสมุดมาตรฐานสามารถเลือกที่จะวินิจฉัยว่าที่จุดลักษณะที่มีการใช้ในกรณีที่พวกเขาต้องการ หากCมีเทมเพลตคอนสตรัคเตอร์เริ่มต้นพร้อม SFINAE แปลก ๆ บางอย่างที่สามารถเปลี่ยนคำตอบได้ถ้าBทำเสร็จแล้วแตกต่างกันแน่นอนว่าลักษณะนั้นขึ้นอยู่กับมัน
TC
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.