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?


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().


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

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


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
{
    // ...
};

Solução interessante. Mas agora é preciso usar this->dataou using B_data::data;acessar por datadentro B.
Evg
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.