Сховати порожній базовий клас для сукупної ініціалізації


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

Чому це коментар? Здається, працює, lol
user7769147

Це таке очевидне рішення, що я подумав, що ти маєш певні причини не використовувати його. :)
Evg

Це було занадто просто xD. Якщо ви
напишете

Відповіді:


6

Найпростіше рішення - додати варіативний конструктор:

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інші елементи в масиві dataбудуть ініціалізовані за значенням як T().


3
Я щойно з’ясував, чому це відрізняється від сукупної ініціалізації. Якщо ви вважаєте B<Class, 5> b = {Class()}; Class, що спочатку буде побудовано, а потім переміщено, тоді як за допомогою сукупної ініціалізації Classбуде побудовано місце, жодного переміщення не буде задіяно
user7769147

@ user7769147, хороший момент. Ви можете брати std::tupleаргументи і використовувати їх для побудови об'єктів на місці. Але синтаксис буде досить громіздким.
Evg

1
Я випадково знайшов рішення, яке вирішує цю проблему, я залишаю це як прийняту відповідь, щоб подякувати вам за вашу доступність :).
користувач7769147

4

Оскільки C ++ 20 ви можете використовувати призначені ініціалізатори в сукупній ініціалізації .

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

Це все ще занадто багатослівно для мене, це був мінімальний приклад. Мій учасник масиву має дивне ім’я, яке повинен ігнорувати користувач
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: Дійсно, спрощено.
Побоявся

2

Я знайшов інше рішення, яке (не знаю як) працює ідеально і вирішує проблему, про яку ми обговорювали під відповіддю Евга

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.