Ẩn lớp cơ sở trống để khởi tạo tổng hợp


9

Hãy xem xét các mã sau đây:

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

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

Đây là cách bạn phải khởi tạo B: B<int, 3> b = { {}, {1, 2, 3} }; Tôi muốn tránh {} trống không cần thiết cho lớp cơ sở. Có một giải pháp được Jarod42 đề xuất ở đây , tuy nhiên, nó không hoạt động với khởi tạo mặc định của các phần tử: B<int, 3> b = {1, 2, 3};vẫn ổn nhưng B<int, 3> b = {1};không phải: b.data[1]b.data[2]không được mặc định khởi tạo thành 0 và xảy ra lỗi trình biên dịch. Có cách nào (hoặc sẽ có với c ++ 20) để "ẩn" lớp cơ sở khỏi xây dựng không?


2
Tại sao không thêm một constructor template<class... Ts> B(Ts... args) : data{args...} {}?
Evg

Tại sao nó là một bình luận? Nó dường như đang hoạt động, lol
user7769147

Đây là một giải pháp rõ ràng đến mức tôi nghĩ bạn có một số lý do để không sử dụng nó. :)
Evg

Thật quá dễ dàng xD. Nếu bạn viết nó như một câu trả lời, tôi sẽ chấp nhận nó
user7769147

Câu trả lời:


6

Giải pháp đơn giản nhất là thêm một hàm tạo matrixdic:

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};
}

Nếu bạn cung cấp ít phần tử hơn trong {...}danh sách khởi tạo hơn N, các phần tử còn lại trong mảng datasẽ được khởi tạo giá trị theo T().


3
Tôi vừa tìm ra lý do tại sao điều này khác với khởi tạo tổng hợp. Nếu bạn xem xét B<Class, 5> b = {Class()}; Classsẽ được xây dựng trước và sau đó di chuyển, trong khi bằng cách sử dụng khởi tạo tổng hợp Classsẽ được xây dựng tại chỗ, không có động thái nào liên quan
user7769147

@ user7769147, điểm tốt. Bạn có thể lấy std::tuplecác đối số và sử dụng chúng để xây dựng các đối tượng tại chỗ. Nhưng cú pháp sẽ khá cồng kềnh.
Evg

1
Tôi đã ngẫu nhiên tìm thấy một giải pháp giải quyết vấn đề này, tôi sẽ để câu trả lời này là câu trả lời được chấp nhận để cảm ơn vì sự sẵn có của bạn :).
dùng7769147

4

Vì C ++ 20, bạn có thể sử dụng các công cụ khởi tạo được chỉ định trong khởi tạo tổng hợp .

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

Điều đó vẫn còn quá dài dòng đối với tôi, đó là một ví dụ tối thiểu. Thành viên mảng của tôi có một tên lạ nên bị người dùng bỏ qua
user7769147

4

Vẫn với constructor, bạn có thể làm một cái gì đó như:

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];
};

Bản giới thiệu

SFINAE được thực hiện chủ yếu để tránh tạo hàm giả sao chép B(B&).

Bạn sẽ cần thêm thẻ riêng để hỗ trợ B<std::index_sequence<0, 1>, 42>;-)


Tại sao bạn cần ((void)Is, T())...? Điều gì nếu bạn chỉ đơn giản là bỏ qua nó? T()Theo mặc định, các phần tử còn lại có được khởi tạo giá trị không?
Evg

1
@Evg: Thật vậy, đơn giản hóa. Đã sợ chỉ mặc định khởi tạo các phần tử còn lại thay vì khởi tạo giá trị chúng ...
Jarod42

2

Tôi đã tìm thấy một giải pháp khác mà (tôi không biết làm thế nào) hoạt động hoàn hảo và giải quyết vấn đề chúng ta đang thảo luận dưới câu trả lời của 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
{
    // ...
};

Giải pháp thú vị. Nhưng bây giờ người ta phải sử dụng this->datahoặc using B_data::data;truy cập datavào bên trong B.
Evg
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.