Ukryj pustą klasę bazową do inicjalizacji agregatów

9

Rozważ następujący kod:

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

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

Oto jak zainicjować B: B<int, 3> b = { {}, {1, 2, 3} }; Chcę uniknąć niepotrzebnego pustego {} dla klasy podstawowej. Jest to rozwiązanie proponowane przez Jarod42 tutaj , jednak nie działa z domyślnych elementów inicjacji: B<int, 3> b = {1, 2, 3};jest w porządku, ale B<int, 3> b = {1};nie jest: b.data[1]i b.data[2]nie są domyślnie ustawiony na 0 i wystąpi błąd kompilatora. Czy jest jakiś sposób (lub będzie z c ++ 20), aby „ukryć” klasę bazową przed budową?

użytkownik7769147
źródło
2
Dlaczego nie dodać konstruktora template<class... Ts> B(Ts... args) : data{args...} {}?
Evg
Dlaczego to jest komentarz? Wygląda na to, że działa, lol
user7769147
To tak oczywiste rozwiązanie, że myślałem, że masz powód, aby z niego nie korzystać. :)
Evg
To było zbyt łatwe xD. Jeśli napiszesz to jako odpowiedź, zaakceptuję
7769147

Odpowiedzi:

6

Najłatwiejszym rozwiązaniem jest dodanie konstruktora 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};
}

Jeśli podasz mniej elementów na {...}liście inicjalizującej niż N, pozostałe elementy w tablicy datazostaną zainicjalizowane wartościami przez T().

Evg
źródło
3
Właśnie odkryłem, dlaczego różni się to od agregacji inicjalizacji. Jeśli weźmiesz pod uwagę, B<Class, 5> b = {Class()}; Classże zostanie najpierw zbudowany, a następnie przeniesiony, podczas gdy przy użyciu agregacji inicjującej Classbyłby zbudowany, nie będzie żadnego ruchu
7769147
@ user7769147, dobry punkt. Możesz wziąć std::tupleargumenty i użyć ich do budowy obiektów w miejscu. Ale składnia będzie raczej nieporęczna.
Evg
1
Losowo znalazłem rozwiązanie, które rozwiązuje ten problem, pozostawiam jako zaakceptowaną odpowiedź, aby podziękować za twoją dostępność :).
user7769147
4

Od wersji C ++ 20 można używać wyznaczonych inicjatorów do agregacji inicjalizacji .

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
songyuanyao
źródło
To wciąż dla mnie zbyt szczegółowe, to był minimalny przykład. Członek mojej tablicy ma dziwną nazwę, którą powinien zignorować użytkownik
user7769147
4

Nadal z konstruktorem możesz zrobić coś takiego:

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

Próbny

SFINAE odbywa się głównie w celu uniknięcia tworzenia konstruktora pseudo-kopii B(B&).

Potrzebujesz dodatkowego prywatnego tagu do wsparcia B<std::index_sequence<0, 1>, 42>;-)

Jarod42
źródło
Dlaczego musisz ((void)Is, T())...? Co jeśli po prostu to pominiesz? Czy T()domyślnie nie zostaną zainicjowane pozostałe elementy ?
Evg
1
@Evg: Rzeczywiście, uproszczone. Bał się tylko domyślnie zainicjować pozostałe elementy zamiast wartości zainicjować je ...
Jarod42
2

Znalazłem inne rozwiązanie, które (nie wiem jak) działa idealnie i rozwiązuje problem, który omawialiśmy pod odpowiedzią Evga

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
{
    // ...
};
użytkownik7769147
źródło
Ciekawe rozwiązanie Ale teraz trzeba użyć this->datalub using B_data::data;wejść do dataśrodka B.
Evg