Ocultar classe base vazia para inicialização agregada

9

Considere o seguinte código:

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

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

É assim que você precisa inicializar B: B<int, 3> b = { {}, {1, 2, 3} }; quero evitar o vazio desnecessário {} para a classe base. Há uma solução proposta por Jarod42 aqui , no entanto, ela não funciona com a inicialização padrão dos elementos: B<int, 3> b = {1, 2, 3};está bem, mas B<int, 3> b = {1};não está: b.data[1]e b.data[2]não é inicializada com o padrão como 0, e ocorre um erro do compilador. Existe alguma maneira (ou haverá com c ++ 20) de "ocultar" a classe base da construção?

user7769147
fonte
2
Por que não adicionar um construtor template<class... Ts> B(Ts... args) : data{args...} {}?
Evg
Por que é um comentário? Parece estar funcionando, lol #
7777147
Essa é uma solução tão óbvia que achei que você tivesse algum motivo para não usá-la. :)
Evg
Foi muito fácil xD. Se você escrever isso como uma resposta, eu aceito
#

Respostas:

6

A solução mais fácil é adicionar um construtor variável:

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

Se você fornecer menos elementos na {...}lista do inicializador do que N, os elementos restantes na matriz dataserão inicializados por valor como antes T().

Evg
fonte
3
Acabei de descobrir por que isso é diferente da inicialização agregada. Se você considerar B<Class, 5> b = {Class()}; Classque será construído primeiro e depois movido, enquanto o uso de inicialização agregada Classfosse construído, não haverá movimentação envolvida
user7769147
@ user7769147, bom ponto. Você pode pegar std::tupleem argumentos e usá-los para construir objetos no local. Mas a sintaxe será bastante complicada.
Evg
11
Encontrei aleatoriamente uma solução que resolve esse problema. Vou deixar isso como resposta aceita para agradecer sua disponibilidade :).
user7769147
4

Desde o C ++ 20, você pode usar inicializadores designados na inicialização agregada .

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
fonte
Ainda é muito detalhado para mim, esse foi um exemplo mínimo. Meu membro da matriz tem um nome estranho que deve ser ignorado pelo usuário
user7769147
4

Ainda com o construtor, você pode fazer algo como:

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

Demo

O SFINAE é feito principalmente para evitar a criação de pseudo-construtor de cópias B(B&).

Você precisaria de uma tag privada extra para apoiar B<std::index_sequence<0, 1>, 42>;-)

Jarod42
fonte
Por que você precisa ((void)Is, T())...? E se você simplesmente omiti-lo? Os elementos restantes não serão inicializados T()por valor por padrão?
Evg
11
@ Evg: De fato, simplificado. Foi medo de padrão apenas initialize elementos em vez de valor remanescente inicializa-la ...
Jarod42
2

Encontrei outra solução que (não sei como) funciona perfeitamente e resolve o problema que estávamos discutindo sob a resposta de 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
{
    // ...
};
user7769147
fonte
Solução interessante. Mas agora é preciso usar this->dataou using B_data::data;acessar por datadentro B.
Evg