I would like to write some code like the following:
using int_list_t = std::initializer_list<int>;
struct ThreeDimensionalBox {
static constexpr int_list_t kDims = {1, 2, 3};
};
struct FourDimensionalBox {
static constexpr int_list_t kDims = {4, 5, 6, 7};
};
template<typename Box1, typename Box2>
struct CombinedBox {
static constexpr int_list_t kDims = Box1::kDims Box2::kDims; // error
};
using SevenDimensionalBox = CombinedBox<ThreeDimensionalBox, FourDimensionalBox>;
Is there some way to fix the implementation of CombinedBox
, so that SevenDimensionalBox::kDims
is effectively bound to {1, 2, 3, 4, 5, 6, 7}
?
I know that I can replace std::initializer_list<int>
with a custom template class with a variadic int template parameter list, with concatenation effectively achieved via standard metaprogramming recursion techniques. I was just wondering if a solution exists using only std::initializer_list
.
CodePudding user response:
std::initializer_list
can be initialized only as empty, with a list of brace-enclosed elements, or by copy.
However even with a copy construction, the lifetime of the actual array that std::initializer_list
references is determined by the lifetime of the original std::initializer_list
object that was initialized by a brace-enclosed element list. In other words copying the std::initializer_list
does not copy the array or extend its lifetime.
Therefore it is impossible to concatenate std::initializer_list
s. They are not meant to be used as a container. A std::initializer_list
should mostly be used only as a function parameter as a lightweight way to pass a brace-enclosed list of elements of unspecified size to a function for it to then process the elements further.
You probably want std::array
instead. Something like this (requiring C 17, but not C 20):
struct ThreeDimensionalBox {
static constexpr auto kDims = std::array{1, 2, 3};
};
struct FourDimensionalBox {
static constexpr auto kDims = std::array{4, 5, 6, 7};
};
template<typename T, std::size_t N, std::size_t M>
constexpr auto concat_arrays(const std::array<T, N>& a, const std::array<T, M>& b) {
// assumes `T` is default-constructible
std::array<T, N M> r;
std::copy(std::begin(a), std::end(a), std::begin(r));
std::copy(std::begin(b), std::end(b), std::begin(r) N);
return r;
}
template<typename Box1, typename Box2>
struct CombinedBox {
static constexpr auto kDims = concat_arrays(Box1::kDims, Box2::kDims);
};
using SevenDimensionalBox = CombinedBox<ThreeDimensionalBox, FourDimensionalBox>;
CodePudding user response:
std::initializer_list
has exactly and only one purpose: to be a tool for initializing an object, within the scope where a braced-init-list (the stuff in {}
) was used for the initialization of that object. Everything about this type is built for that purpose, and it has a bunch of firewalls in place to prevent you from doing basically anything else with it.
The most important of which is that initializer_list
doesn't actually store anything; it references an array of values which is stored elsewhere (namely, the cite of the {}
that created it). Since it does not store the array, and they're only meant to be build from the {}
grammatical construct, you cannot give them an array to reference. They can only get an array from an existing initializer_list
object via copying, and that one could only get one either from a {}
or an existing initializer_list
which could only get one from... well, you get the idea.
If you need to do compile-time array manipulation, std::array
is a better tool. If you want to concatenate two braced-init-lists and use the result to initialize some object... you can't do that.
Do not try to use initializer_list
as a quick-and-dirty array type. Only use it for initializing objects.