How to avoid {} when using aggregate initialization with empty base class
Asked Answered
E

1

12

C++17's aggregate initialization for base class is awesome, but it is verbose when the base is only there to provide some functions (so no data members).

Here is minimal example:

#include <cstddef>
struct base_pod
{
    // functions like friend compare operator
};
template<typename T, std::size_t N>
struct der_pod : public base_pod
{
    T k[N];
};

int main()
{
    der_pod<int, 2> dp {{}, {3, 3} };
}

As the example above shows, I have to provide empty {}, otherwise compile error will occur. live demo. If I omit it:

prog.cc:15:28: error: initializer for aggregate with no elements requires explicit braces
        der_pod<int, 2> dp{3, 3};
                           ^
prog.cc:15:31: warning: suggest braces around initialization of subobject [-Wmissing-braces]
        der_pod<int, 2> dp{3, 3};
                              ^
                              {}
1 warning and 1 error generated.

Any workaround or pre-C++17 way?

Esque answered 9/4, 2019 at 12:6 Comment(6)
I would notice that there is nothing related with having base class in your example, you can remove it and see that the error is still there godbolt.org/z/0FpuQ5Mandeville
@DmitryGordon I need this base class. the case I provided is just a minimal exampleEsque
Pre C++17 you can't have base classes so it isn't a problem. You would just put the class and all of its operators in a namespace.Filament
What exactly are you using the base class for? The comparison functions have to overridden in the derived class for them to work.Filament
Regarding the errors you show, it's the same with e.g. std::array: You need an outer pair of braces for the object itself, then an inner pair for the aggregate data. As in der_pod<int, 2> dp{{3, 3}};. But that won't work because the inheritance and the need to initialize the base class as well (leading to that initial empty {}).Aude
@Filament Sorry for the late reply. if the base is a templated class, CRTP will be useful hereEsque
R
3

You can still provide constructor, for example:

template <typename T, std::size_t N> using always_t = T;

struct base_pod
{
    // functions like friend compare operator
};
template<typename T, typename Seq> struct der_pod_impl;

template<typename T, std::size_t ... Is>
struct der_pod_impl<T, std::index_sequence<Is...>> : base_pod
{
    der_pod_impl(always_t<T, Is>... args) : k{args...} {}

    T k[sizeof...(Is)];
};

template<typename T, std::size_t N>
using der_pod = der_pod_impl<T, std::make_index_sequence<N>>;

Demo

Revivify answered 9/4, 2019 at 19:9 Comment(3)
Why go through all the trouble of splitting der_pod into an alias template and _impl variant, specializing it, and writing a customized type identity type trait? Couldn't a deduction guide accomplish all of that while also enabling CTAD (so no need to explicitly write out the type and size)?Shadshadberry
@303: variadic template constructor might indeed be an alternative, but there is caveat with that to avoid issues with copy constructor.Revivify
Ah, good point! But I do think the alternative version, with explicitly defaulted copy constructor, is not too bad: godbolt.org/z/o6j4sjPc6. Unless you find something inherently wrong with it, I think it should be added to your answer +1.Shadshadberry

© 2022 - 2024 — McMap. All rights reserved.