(Note: This question is about not having to specify the number of elements and still allow nested types to be directly initialized.)
This question discusses the uses left for a C array like int arr[20];
. On his answer, @James Kanze shows one of the last strongholds of C arrays, it's unique initialization characteristics:
int arr[] = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 };
We don't have to specify the number of elements, hooray! Now iterate over it with the C++11 functions std::begin
and std::end
from <iterator>
(or your own variants) and you never need to even think of its size.
Now, are there any (possibly TMP) ways to achieve the same with std::array
? Use of macros allowed to make it look nicer. :)
??? std_array = { "here", "be", "elements" };
Edit: Intermediate version, compiled from various answers, looks like this:
#include <array>
#include <utility>
template<class T, class... Tail, class Elem = typename std::decay<T>::type>
std::array<Elem,1+sizeof...(Tail)> make_array(T&& head, Tail&&... values)
{
return { std::forward<T>(head), std::forward<Tail>(values)... };
}
// in code
auto std_array = make_array(1,2,3,4,5);
And employs all kind of cool C++11 stuff:
- Variadic Templates
sizeof...
- rvalue references
- perfect forwarding
std::array
, of course- uniform initialization
- omitting the return type with uniform initialization
- type inference (
auto
)
And an example can be found here.
However, as @Johannes points out in the comment on @Xaade's answer, you can't initialize nested types with such a function. Example:
struct A{ int a; int b; };
// C syntax
A arr[] = { {1,2}, {3,4} };
// using std::array
??? std_array = { {1,2}, {3,4} };
Also, the number of initializers is limited to the number of function and template arguments supported by the implementation.
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.
1. No need to rely on RVO
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:we should do:
2. Make
make_array
aconstexpr
functionThis allow us to create compile-time constant arrays.
3. No need to check that all arguments are of the same type
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,If we are simply
static_assert
ing thata
,b
, andc
have the same type, then this check will fail, but that probably isn't what we'd expect. Instead, we should compare theirstd::decay_t<T>
types (which are allint
s)).4. Deduce the array value type by decaying the forwarded arguments
This is similar to point 3. Using the same code snippet, but don't specify the value type explicitly this time:
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 astd::array<T, …>
, return astd::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 anarray<const int, …>
, we would use aconst array<int, …>
anyway. There is a trade-off, but I think a reasonable one. The C++17std::make_optional
also takes this approach:Taking the above points into account, a full working implementation of
make_array
in C++14 looks like this:Usage:
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.Combining a few ideas from previous posts, here's a solution that works even for nested constructions (tested in GCC4.6):
Strangely, can cannot make the return value an rvalue reference, that would not work for nested constructions. Anyway, here's a test:
(For the last output I'm using my pretty-printer.)
Actually, let us improve the type safety of this construction. We definitely need all types to be the same. One way is to add a static assertion, which I've edited in above. The other way is to only enable
make_array
when the types are the same, like so:Either way, you will need the variadic
all_same<Args...>
type trait. Here it is, generalizing fromstd::is_same<S, T>
(note that decaying is important to allow mixing ofT
,T&
,T const &
etc.):Note that
make_array()
returns by copy-of-temporary, which the compiler (with sufficient optimisation flags!) is allowed to treat as an rvalue or otherwise optimize away, andstd::array
is an aggregate type, so the compiler is free to pick the best possible construction method.Finally, note that you cannot avoid copy/move construction when
make_array
sets up the initializer. Sostd::array<Foo,2> x{Foo(1), Foo(2)};
has no copy/move, butauto x = make_array(Foo(1), Foo(2));
has two copy/moves as the arguments are forwarded tomake_array
. I don't think you can improve on that, because you can't pass a variadic initializer list lexically to the helper and deduce type and size -- if the preprocessor had asizeof...
function for variadic arguments, perhaps that could be done, but not within the core language.