Home > Net >  Is it safe to define a specialization for std::array
Is it safe to define a specialization for std::array

Time:12-21

I am working on a library that has a class foo. foo has a non-trivial constructor. When I create an std::array of foo (std::array<foo, 10>), the constructor is called 10 times. I want to implement a separate way of initializing an arrays of foo. Is it safe to define a specialization for std::array<foo, N>?

https://en.cppreference.com/w/cpp/language/extending_std says specializations for custom types are allowed unless explicitly disallowed and https://en.cppreference.com/w/cpp/container/array doesn't say anything about it. Is it safe to define such a specialization? If yes, what properties does my specialization need to have?

CodePudding user response:

Yes, you can specialize std::array<foo, N> for a program-defined foo. However, there are multiple problems with this, only giving two major ones that came to mind here:

  1. Every user of std::array<foo, N> must include the partial specialization before the first use that would instantiate std::array<foo, N>. Otherwise behavior is undefined. So if one translation unit or library uses std::array<foo, N> without including the specialization, you have a problem. Even from a more practical perspective (instead the standard's UB) you are likely to have an ABI break between the libraries/translation units in this case. In other words, the only safe place to put the specialization is in the header that provides foo, which would inside the library.

  2. Your specialization must meet the requirements that the standard puts on a standard library implementation of std::array. One of these requirements is that std::array is an aggregate type. This means that you can't provide a custom constructor to the class, making your goal impossible.

Instead define your own container type that behaves the way you want, although I question what exactly you have in mind. It is likely that what you want is as complex as std::vector and that you would be better served by it. Sometimes a std::vector with a maximum size fully stack allocated is also nice, but the standard library doesn't have that. It can however be emulated with a custom stack allocator together with std::vector.

CodePudding user response:

Disclaimer: I'm not sure if this fails any other requirements on specializations in std:: but it should deal with the aggregate requirement on std::array.


A specialization of std::array for foo (or template <class T> class dyn_var that you mentioned later) would have to be be an aggregate, so you can't add a constructor.

However, aggregates of aggregates don't require extra braces when initializing them. Consider:

template<class T, std::size_t N>
struct array {
    struct inner_array {
        T data[N];
    };
    inner_array m_data;
};

array<int, 3> var{1, 2, 3}; // initializes data in m_data

I think this opens up for a specialization that fulfills the aggregate requirement. Here's my version of dyn_var<T>:

namespace builder {
struct cheap_tag_t {} cheap_tag;

template <class T>
class dyn_var {
public:
    dyn_var() { std::cout << "expensive ctor\n"; }
    dyn_var(T) { std::cout << "T ctor\n"; }

    dyn_var(cheap_tag_t) { std::cout << "cheap ctor\n"; }
};
}  // namespace builder

And one possible std::array specialization that uses the cheap constructor:

template <class T, std::size_t N>
struct std::array<builder::dyn_var<T>, N> {
    struct inner_array {
        builder::dyn_var<T> data[N];
    };

    inner_array m_data{
        []<std::size_t... I>(std::index_sequence<I...>){
            // RVO:
            return inner_array{((void)I, builder::cheap_tag)...};
        }(std::make_index_sequence<N>())
    };

    // other member functions ...
};

Now these would both use the cheap constructor:

std::array<builder::dyn_var<int>, 10> foos1;
std::array<builder::dyn_var<int>, 10> foos2{};

while this would use the T ctor for the first 5 elements and the expensive constructor for the remaining elements:

std::array<builder::dyn_var<int>, 10> foos3{1,2,3,4,5};

The last point, that only partially initializing the std::array triggers the default construction of the rest of the elements may be a show stopper since it will certainly confuse the end users, but I thought this may be worth considering nevertheless.

Demo

  • Related