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:
Every user of
std::array<foo, N>
must include the partial specialization before the first use that would instantiatestd::array<foo, N>
. Otherwise behavior is undefined. So if one translation unit or library usesstd::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 providesfoo
, which would inside the library.Your specialization must meet the requirements that the standard puts on a standard library implementation of
std::array
. One of these requirements is thatstd::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.