(Note: This question is about not having to specify the number of elements and still allow nested types to be directly initialized.)
This question discu
If std::array is not a constraint and if you have Boost, then take a look at list_of(). This is not exactly like C type array initialization that you want. But close.
С++17 compact implementation.
template <typename... T>
constexpr auto array_of(T&&... t) {
return std::array{ static_cast<std::common_type_t<T...>>(t)... };
}
C++11 will support this manner of initialization for (most?) std containers.
I know it's been quite some time since this question was asked, but I feel the existing answers still have some shortcomings, so I'd like to propose my slightly modified version. Following are the points that I think some existing answers are missing.
Some answers mention that we need to rely on RVO to return the constructed array
. That is not true; we can make use of copy-list-initialization to guarantee there will never be temporaries created. So instead of:
return std::array<Type, …>{values};
we should do:
return {{values}};
make_array
a constexpr
functionThis allow us to create compile-time constant arrays.
First off, if they are not, the compiler will issue a warning or error anyway because list-initialization doesn't allow narrowing. Secondly, even if we really decide to do our own static_assert
thing (perhaps to provide better error message), we should still probably compare the arguments' decayed types rather than raw types. For example,
volatile int a = 0;
const int& b = 1;
int&& c = 2;
auto arr = make_array<int>(a, b, c); // Will this work?
If we are simply static_assert
ing that a
, b
, and c
have the same type, then this check will fail, but that probably isn't what we'd expect. Instead, we should compare their std::decay_t<T>
types (which are all int
s)).
This is similar to point 3. Using the same code snippet, but don't specify the value type explicitly this time:
volatile int a = 0;
const int& b = 1;
int&& c = 2;
auto arr = make_array(a, b, c); // Will this work?
We probably want to make an array<int, 3>
, but the implementations in the existing answers probably all fail to do that. What we can do is, instead of returning a std::array<T, …>
, return a std::array<std::decay_t<T>, …>
.
There is one disadvantage about this approach: we can't return an array
of cv-qualified value type any more. But most of the time, instead of something like an array<const int, …>
, we would use a const array<int, …>
anyway. There is a trade-off, but I think a reasonable one. The C++17 std::make_optional also takes this approach:
template< class T >
constexpr std::optional<std::decay_t<T>> make_optional( T&& value );
Taking the above points into account, a full working implementation of make_array
in C++14 looks like this:
#include <array>
#include <type_traits>
#include <utility>
template<typename T, typename... Ts>
constexpr std::array<std::decay_t<T>, 1 + sizeof... (Ts)>
make_array(T&& t, Ts&&... ts)
noexcept(noexcept(std::is_nothrow_constructible<
std::array<std::decay_t<T>, 1 + sizeof... (Ts)>, T&&, Ts&&...
>::value))
{
return {{std::forward<T>(t), std::forward<Ts>(ts)...}};
}
template<typename T>
constexpr std::array<std::decay<T>, 0> make_array() noexcept
{
return {};
}
Usage:
constexpr auto arr = make_array(make_array(1, 2),
make_array(3, 4));
static_assert(arr[1][1] == 4, "!");