Ocultar clase base vacía para inicialización agregada

9

Considere el siguiente código:

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

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

Así es como debe inicializar B: B<int, 3> b = { {}, {1, 2, 3} }; quiero evitar el vacío {} innecesario para la clase base. Hay una solución propuesta por Jarod42 aquí , sin embargo, no funciona con elementos de inicialización por defecto: B<int, 3> b = {1, 2, 3};está bien, pero B<int, 3> b = {1};no lo es: b.data[1]y b.data[2]no son opción predeterminada inicializa a 0 y se produce un error de compilación. ¿Hay alguna forma (o habrá con c ++ 20) para "ocultar" la clase base de la construcción?

usuario7769147
fuente
2
¿Por qué no agregar un constructor template<class... Ts> B(Ts... args) : data{args...} {}?
Evg
¿Por qué es un comentario? Parece estar funcionando, jajaja
user7769147
Esta es una solución tan obvia que pensé que tenías alguna razón para no usarla. :)
Evg
Fue demasiado fácil xD. Si lo escribe como respuesta, lo aceptaré
user7769147

Respuestas:

6

La solución más fácil es agregar un constructor 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};
}

Si proporciona menos elementos en la {...}lista de inicializadores que N, los elementos restantes en la matriz datase inicializarán como valor por T().

Evg
fuente
3
Acabo de descubrir por qué esto es diferente de la inicialización agregada. Si se tiene en cuenta B<Class, 5> b = {Class()}; Classva a ser construido primero y luego se trasladó, mientras que mediante el uso de inicialización de agregados Classsería construida en el lugar, ningún movimiento involucrado
user7769147
@ user7769147, buen punto. Puede tomar std::tupleargumentos y usarlos para construir objetos en el lugar. Pero la sintaxis será bastante engorrosa.
Evg
1
He encontrado al azar una solución que resuelve este problema, lo dejaré como respuesta aceptada para agradecerle su disponibilidad :).
usuario7769147
4

Desde C ++ 20 puede usar inicializadores designados en la inicialización 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
fuente
Eso todavía es demasiado detallado para mí, ese fue un ejemplo mínimo. El miembro de mi matriz tiene un nombre extraño que el usuario debe ignorar
user7769147
4

Aún con el constructor, podrías hacer 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];
};

Manifestación

SFINAE se hace principalmente para evitar crear un constructor de pseudo copia B(B&).

Necesitaría una etiqueta privada adicional para admitir B<std::index_sequence<0, 1>, 42>;-)

Jarod42
fuente
¿Por qué lo necesitas ((void)Is, T())...? ¿Qué pasa si simplemente lo omites? ¿No se inicializarán los elementos restantes T()de forma predeterminada?
Evg
1
@Evg: De hecho, simplificado. Sólo tenía miedo a los valores predeterminados de inicialización elementos en vez de valor restante a inicializar ...
Jarod42
2

He encontrado otra solución que (no sé cómo) funciona a la perfección y resuelve el problema que discutíamos con la respuesta 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
{
    // ...
};
usuario7769147
fuente
Solución interesante Pero ahora hay que usar this->datao using B_data::data;acceder al datainterior B.
Evg