ซ่อนคลาสฐานที่ว่างเปล่าสำหรับการเริ่มต้นรวม


9

พิจารณารหัสต่อไปนี้:

struct A
{
    // No data members
    //...
};

template<typename T, size_t N>
struct B : A
{
    T data[N];
}

นี่คือวิธีที่คุณต้องเริ่มต้น B: B<int, 3> b = { {}, {1, 2, 3} }; ฉันต้องการหลีกเลี่ยงการว่าง {} ที่ไม่จำเป็นสำหรับคลาสฐาน มีวิธีแก้ปัญหาที่เสนอโดย Jarod42 ที่นี่แต่มันไม่ทำงานกับองค์ประกอบการเริ่มต้นองค์ประกอบ: B<int, 3> b = {1, 2, 3};ดี แต่B<int, 3> b = {1};ไม่: b.data[1]และb.data[2]ไม่ได้เริ่มต้นเริ่มต้นที่ 0 และข้อผิดพลาดคอมไพเลอร์เกิดขึ้น มีวิธีใดบ้าง (หรือมี c ++ 20) ในการ "ซ่อน" คลาสพื้นฐานจากการสร้างหรือไม่


2
ทำไมไม่เพิ่มตัวสร้างtemplate<class... Ts> B(Ts... args) : data{args...} {}?
Evg

ทำไมถึงเป็นคอมเม้นท์? ดูเหมือนว่าจะใช้งานได้ฮ่า ๆ
user7769147

นี่เป็นทางออกที่ชัดเจนที่ฉันคิดว่าคุณมีเหตุผลที่จะไม่ใช้ :)
Evg

มันง่ายเกินไป xD ถ้าคุณเขียนมันเป็นคำตอบฉันจะยอมรับมัน
user7769147

คำตอบ:


6

ทางออกที่ง่ายที่สุดคือการเพิ่มตัวสร้าง Variadic:

struct A { };

template<typename T, std::size_t N>
struct B : A {
    template<class... Ts, typename = std::enable_if_t<
        (std::is_convertible_v<Ts, T> && ...)>>
    B(Ts&&... args) : data{std::forward<Ts>(args)...} {}

    T data[N];
};

void foo() {
    B<int, 3> b1 = {1, 2, 3};
    B<int, 3> b2 = {1};
}

ถ้าคุณให้องค์ประกอบน้อยใน{...}รายการ initializer กว่าNองค์ประกอบที่เหลืออยู่ในอาร์เรย์จะมีมูลค่าเริ่มต้นโดยdataT()


3
ฉันเพิ่งค้นพบสาเหตุที่แตกต่างจากการเริ่มต้นรวม หากคุณพิจารณาว่าB<Class, 5> b = {Class()}; Classจะสร้างก่อนแล้วจึงย้ายในขณะที่การใช้การเริ่มต้นรวมClassจะถูกสร้างขึ้นไม่มีการย้ายที่เกี่ยวข้อง
user7769147

@ user7769147 จุดดี คุณสามารถรับstd::tupleข้อโต้แย้งและใช้มันเพื่อสร้างวัตถุในสถานที่ แต่ไวยากรณ์จะค่อนข้างยุ่งยาก
Evg

1
ฉันได้พบวิธีการแก้ปัญหาแบบสุ่มฉันจะปล่อยให้มันเป็นคำตอบที่ได้รับการยอมรับเพื่อขอบคุณสำหรับความพร้อมของคุณ :)
user7769147

4

ตั้งแต่ C ++ 20 คุณสามารถใช้initializers กำหนดในการเริ่มต้นรวม

B<int, 3> b = { .data {1} }; // initialize b.data with {1}, 
                             // b.data[0] is 1, b.data[1] and b.data[2] would be 0

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

4

เมื่อใช้ตัวสร้างคุณอาจทำสิ่งต่อไปนี้:

template<typename T, size_t N>
struct B : A
{
public:
    constexpr B() : data{} {}

    template <typename ... Ts,
              std::enable_if_t<(sizeof...(Ts) != 0 && sizeof...(Ts) < N)
                               || !std::is_same_v<B, std::decay_t<T>>, int> = 0>
    constexpr B(T&& arg, Ts&&... args) : data{std::forward<T>(arg), std::forward<Ts>(args)...}
    {}

    T data[N];
};

การสาธิต

ส่วนใหญ่แล้ว SFINAE จะทำเพื่อหลีกเลี่ยงการสร้างตัวสร้างสำเนาเทียม B(B&)ส่วนใหญ่จะทำเพื่อหลีกเลี่ยงการสร้างตัวสร้างสำเนาหลอก

คุณจะต้องมีแท็กส่วนตัวพิเศษเพื่อรองรับB<std::index_sequence<0, 1>, 42>;-)


ทำไมคุณต้อง((void)Is, T())...? ถ้าคุณไม่สนใจมันล่ะ องค์ประกอบที่เหลือจะไม่ได้รับค่าเริ่มต้นด้วยT()โดยค่าเริ่มต้น?
Evg

1
@Evg: แน่นอนง่ายขึ้น กลัวที่จะเริ่มต้นองค์ประกอบเริ่มต้นที่เหลืออยู่แทนค่าเริ่มต้นเท่านั้น ...
Jarod42

2

ฉันได้พบวิธีแก้ไขปัญหาอื่นที่ (ฉันไม่รู้ว่า) ทำงานได้อย่างสมบูรณ์แบบและแก้ปัญหาที่เราคุยกันภายใต้คำตอบของ Evg

struct A {};

template<typename T, size_t N>
struct B_data
{
    T data[N];
};

template<typename T, size_t N>
struct B : B_data<T, N>, A
{
    // ...
};

ทางออกที่น่าสนใจ แต่ตอนนี้หนึ่งที่มีการใช้this->dataหรือusing B_data::data;การเข้าถึงภายในdata B
Evg
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.