(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
(Solution by @dyp)
Note: requires C++14 (std::index_sequence
). Although one could implement std::index_sequence
in C++11.
#include <iostream>
// ---
#include <array>
#include <utility>
template <typename T>
using c_array = T[];
template<typename T, size_t N, size_t... Indices>
constexpr auto make_array(T (&&src)[N], std::index_sequence<Indices...>) {
return std::array<T, N>{{ std::move(src[Indices])... }};
}
template<typename T, size_t N>
constexpr auto make_array(T (&&src)[N]) {
return make_array(std::move(src), std::make_index_sequence<N>{});
}
// ---
struct Point { int x, y; };
std::ostream& operator<< (std::ostream& os, const Point& p) {
return os << "(" << p.x << "," << p.y << ")";
}
int main() {
auto xs = make_array(c_array<Point>{{1,2}, {3,4}, {5,6}, {7,8}});
for (auto&& x : xs) {
std::cout << x << std::endl;
}
return 0;
}
Best I can think of is:
template<class T, class... Tail>
auto make_array(T head, Tail... tail) -> std::array<T, 1 + sizeof...(Tail)>
{
std::array<T, 1 + sizeof...(Tail)> a = { head, tail ... };
return a;
}
auto a = make_array(1, 2, 3);
However, this requires the compiler to do NRVO, and then also skip the copy of returned value (which is also legal but not required). In practice, I would expect any C++ compiler to be able to optimize that such that it's as fast as direct initialization.
I'd expect a simple make_array
.
template<typename ret, typename... T> std::array<ret, sizeof...(T)> make_array(T&&... refs) {
// return std::array<ret, sizeof...(T)>{ { std::forward<T>(refs)... } };
return { std::forward<T>(refs)... };
}
Using trailing return syntax make_array
can be further simplified
#include <array>
#include <type_traits>
#include <utility>
template <typename... T>
auto make_array(T&&... t)
-> std::array<std::common_type_t<T...>, sizeof...(t)>
{
return {std::forward<T>(t)...};
}
int main()
{
auto arr = make_array(1, 2, 3, 4, 5);
return 0;
}
Unfortunatelly for aggregate classes it requires explicit type specification
/*
struct Foo
{
int a, b;
}; */
auto arr = make_array(Foo{1, 2}, Foo{3, 4}, Foo{5, 6});
In fact this make_array
implementation is listed in sizeof... operator
Thanks to template argument deduction for class templates proposal we can use deduction guides to get rid of make_array
helper
#include <array>
namespace std
{
template <typename... T> array(T... t)
-> array<std::common_type_t<T...>, sizeof...(t)>;
}
int main()
{
std::array a{1, 2, 3, 4};
return 0;
}
Compiled with -std=c++1z
flag under x86-64 gcc 7.0
Create an array maker type.
It overloads operator,
to generate an expression template chaining each element to the previous via references.
Add a finish
free function that takes the array maker and generates an array directly from the chain of references.
The syntax should look something like this:
auto arr = finish( make_array<T>->* 1,2,3,4,5 );
It does not permit {}
based construction, as only operator=
does. If you are willing to use =
we can get it to work:
auto arr = finish( make_array<T>= {1}={2}={3}={4}={5} );
or
auto arr = finish( make_array<T>[{1}][{2}[]{3}][{4}][{5}] );
None of these look like good solutions.
Using variardics limits you to your compiler-imposed limit on number of varargs and blocks recursive use of {}
for substructures.
In the end, there really isn't a good solution.
What I do is I write my code so it consumes both T[]
and std::array
data agnostically -- it doesn't care which I feed it. Sometimes this means my forwarding code has to carefully turn []
arrays into std::array
s transparently.
Combining a few ideas from previous posts, here's a solution that works even for nested constructions (tested in GCC4.6):
template <typename T, typename ...Args>
std::array<T, sizeof...(Args) + 1> make_array(T && t, Args &&... args)
{
static_assert(all_same<T, Args...>::value, "make_array() requires all arguments to be of the same type."); // edited in
return std::array<T, sizeof...(Args) + 1>{ std::forward<T>(t), std::forward<Args>(args)...};
}
Strangely, can cannot make the return value an rvalue reference, that would not work for nested constructions. Anyway, here's a test:
auto q = make_array(make_array(make_array(std::string("Cat1"), std::string("Dog1")), make_array(std::string("Mouse1"), std::string("Rat1"))),
make_array(make_array(std::string("Cat2"), std::string("Dog2")), make_array(std::string("Mouse2"), std::string("Rat2"))),
make_array(make_array(std::string("Cat3"), std::string("Dog3")), make_array(std::string("Mouse3"), std::string("Rat3"))),
make_array(make_array(std::string("Cat4"), std::string("Dog4")), make_array(std::string("Mouse4"), std::string("Rat4")))
);
std::cout << q << std::endl;
// produces: [[[Cat1, Dog1], [Mouse1, Rat1]], [[Cat2, Dog2], [Mouse2, Rat2]], [[Cat3, Dog3], [Mouse3, Rat3]], [[Cat4, Dog4], [Mouse4, Rat4]]]
(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:
template <typename T, typename ...Args>
typename std::enable_if<all_same<T, Args...>::value, std::array<T, sizeof...(Args) + 1>>::type
make_array(T && t, Args &&... args)
{
return std::array<T, sizeof...(Args) + 1> { std::forward<T>(t), std::forward<Args>(args)...};
}
Either way, you will need the variadic all_same<Args...>
type trait. Here it is, generalizing from std::is_same<S, T>
(note that decaying is important to allow mixing of T
, T&
, T const &
etc.):
template <typename ...Args> struct all_same { static const bool value = false; };
template <typename S, typename T, typename ...Args> struct all_same<S, T, Args...>
{
static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value && all_same<T, Args...>::value;
};
template <typename S, typename T> struct all_same<S, T>
{
static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value;
};
template <typename T> struct all_same<T> { static const bool value = true; };
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, and std::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. So std::array<Foo,2> x{Foo(1), Foo(2)};
has no copy/move, but auto x = make_array(Foo(1), Foo(2));
has two copy/moves as the arguments are forwarded to make_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 a sizeof...
function for variadic arguments, perhaps that could be done, but not within the core language.