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?


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


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

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


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

Solución interesante Pero ahora hay que usar this->datao using B_data::data;acceder al datainterior B.
Evg
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.