Related
I'm looking for a solution using only native C++ language features (up to C++17) for accomplishing the following:
std::array<Type, unsigned int Elem> array_{Type(), // 1 - Call constructor on Type()
Type(), // 2 - ...
... , // 3 - ...
Type()} // Elems - Call the Elem:th Type() constructor
In addition, what I'd also like is that each constructor call should be able to take an arbitrary number of arguments.
A concrete example would be to automate the writing of the following:
std::array<std::shared_ptr<int>, 4> array_{std::make_shared<int>(),
std::make_shared<int>(),
std::make_shared<int>(),
std::make_shared<int>()}
I.e., provided that I know Type and Elem, I'd like to automate the process of creating the brace-enclosed initializer list and in the process call Type:s constructor.
Any ideas?
Update, the real problem I'd like to solve is the following:
template <typename Type, unsigned int Size>
class Storage {
public:
Storage(std::initializer_list<Type> initializer) : array_{initializer} {}
private:
std::array<Type, Size> array_;
};
void foo(){
Storage<std::shared_ptr<int>, 100> storage(...);
// or perhaps
auto storage = std::make_shared<Storage<std::shared_ptr<int>, 100>>(here be an initializer list containing calls to 100 std::make_shared<int>());
}
Like this:
#include <array>
#include <memory>
#include <utility>
template <std::size_t ...I>
std::array<std::shared_ptr<int>, sizeof...(I)> foo(std::index_sequence<I...>)
{
return {(void(I), std::make_shared<int>())...};
}
std::array<std::shared_ptr<int>, 4> array_ = foo(std::make_index_sequence<4>());
Guaranteed copy elision from C++17 ensures that the array is constructed in place, and no extra moves happen.
The return statement expands to {(void(0), std::make_shared<int>()), (void(1), std::make_shared<int>())...}. void(...) is not strictly necessary, but Clang emits a warning otherwise.
But if it was my code, I'd write a more generic helper instead:
#include <utility>
template <typename R, typename N, typename F, N ...I>
[[nodiscard]] R GenerateForEach(std::integer_sequence<N, I...>, F &&func)
{
return {(void(I), func(std::integral_constant<N, I>{}))...};
}
template <typename R, auto N, typename F>
[[nodiscard]] R Generate(F &&func)
{
return (GenerateForEach<R, decltype(N)>)(std::make_integer_sequence<decltype(N), N>{}, std::forward<F>(func));
}
Then:
auto array_ = Generate<std::array<std::shared_ptr<int>, 4>, 4>([](auto){return std::make_shared<int>();});
While the lambda discards the index in this case, it often ends up being useful, especially given that in [](auto index){...}, index.value is constexpr.
I have the following templated code
#include <vector>
#include <array>
#include <iostream>
template<typename T1>
void foo(std::vector<T1> bar) {
std::cout << "GENERIC" << std::endl;
}
template<typename T1>
void foo(std::vector<std::vector<T1>> bar) {
std::cout << "SPECIFIC (vector)" << std::endl;
}
template<typename T1, int SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
std::cout << "SPECIFIC (array)" << std::endl;
}
int main() {
std::vector<std::vector<int>> a(2, std::vector<int> { 1, 2, 3});
std::vector<std::array<int, 3>> b(2, std::array<int, 3> {4, 5, 6});
foo(a);
foo(b);
}
which produces
SPECIFIC (vector)
GENERIC
I'm wondering why the vector-of-vector version is called with the specific template, but the vector-of-array version is called with the generic?
template<typename T1, size_t SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
std::cout << "SPECIFIC (array)" << std::endl;
}
You should use std::size_t instead of int.
run here
Edit :
Actually, your comments and my intuition about the code led me to dig into the topic. At first glance, a standard developer ( like me ) expect compiler to convert int to std::size_t(because they are both integral type and implicitly converting is very trivial) and select void foo(std::vector<std::array<T1, SIZE>> bar) as best specialization. So while reading template argument deduction page i found this :
If a non-type template parameter is used in the parameter list, and
the corresponding template argument is deduced, the type of the
deduced template argument ( as specified in its enclosing template
parameter list, meaning references are preserved) must match the type
of the non-type template parameter exactly, except that cv-qualifiers
are dropped, and except where the template argument is deduced from an
array bound—in that case any integral type is allowed, even bool
though it would always become true:
As always, of course, you must read few more times than once to understand what it means :)
So an interesting result comes out.
Already our desired specialization is not selected but if the compiler had been forced to select, it would be an error.
template<typename T1, int SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
std::cout << "SPECIFIC (array)" << std::endl;
}
int main() {
std::vector<std::array<int, 3>> b(2, std::array<int, 3> {4, 5, 6});
foo(b); // P = std::vector<std::array<int,(int)SIZE>
// A = std::vector<std::array<int,(unsigned_long)SIZE>>
// error: deduced non-type template argument does not have the same
// type as its corresponding template argument */
}
run code
Another interesting thing is :
If the non-type template argument had not been deduced, there would be no restriction which forces argument and template types to be same.
#include <vector>
#include <array>
#include <iostream>
template<typename T1, int SIZE>
void foo(std::vector<std::array<T1, SIZE>> bar) {
std::cout << "SPECIFIC (array)" << std::endl;
}
int main() {
std::vector<std::array<int, 3>> b(2, std::array<int, 3> {4, 5, 6});
foo<int,3>(b);
}
run code
I think this is simply due to one line from [temp.deduct.call]/4
In general, the deduction process attempts to find template argument values that will make the deduced A identical to A
To clarify, A means the parameter, from [temp.deduct.call]/1
...template argument deduction with the type of the corresponding argument of the call (call it A)...
As has already been pointed out, changing template<typename T1, int SIZE> to template<typename T1, size_t SIZE> fixes the issue you are seeing. As stated in [temp.deduct.call]/4, the compiler is looking to deduce an A that is identical to A. Since an std::array has template arguments <class T, size_t N> (from [array.syn]), it's second parameter is in fact size_t, not int.
Therefore, for the template deduction, your generic function of template<typename T1> is able to match exactly the type of A, where-as your specialized template<typename T1, int SIZE> is not an exact match. I believe MSVC is incorrect in its deduction.
I would like to have a class like this:
template<typename T>
struct Foo {
T* data_;
template<typename... Ts, std::enable_if<std::is_same<T,Ts>...>...>
explicit Foo(Ts...ts) : data_{ ts... } {}
};
However; something with the syntax is wrong, and I'm not sure if you can set parameters into a pointer directly like this upon initialization.
What I would like for this to do is simply this:
Foo<int> f1{ 1, 3, 5, 7 }; // Or
// Foo<int> f1( 1, 3, 5 7 );
// f1.data_[0] = 1
// f1.data_[1] = 3
// f1.data_[2] = 5
// f1.data_[3] = 7
// f1.data_[4] = ... not our memory either garbage or undefined...
Foo<float> f2{ 3.5f, 7.2f, 9.8f }; // Or
// Foo<float> f2( 3.5f, 7.2f, 9.8f );
// f2.data_[0] = 3.5
// f2.data_[1] = 7.2
// f2.data_[2] = 9.8
// f2.data_[3] = ... not our memory
I would also like to have the constructor check to make sure that each and every parameter that is passed into the constructor is of type <T>; simply put for each Ts it must be a T.
I might be overthinking this but for the life of me I can not get this or something similar to compile. I don't know if it's within enable_if, is_same or through the class's initializer list and trying to store the contents into a pointer. I don't know if I should use an array of T instead but the array's size won't be known until the arguments are passed into the constructor. I'm also trying to do this without using a basic container such as std::vector; it's more for self education than practical source code. I just want to see how this could be done with raw pointers.
Edit
I've changed my class to something like this:
template<typename T>
struct Foo {
T* data_;
template<typename... Ts, std::enable_if_t<std::is_same<T, Ts...>::value>* = nullptr>
explicit Foo( const Ts&&... ts ) : data_{ std::move(ts)... } {}
};
And when trying to use it:
int a = 1, b = 3, c = 5, d = 7;
Foo<int> f1( a, b, c, d );
Foo<int> f2{ a, b, c, d };
I'm a little closer with this iteration; but they both give different compiler errors.
The first being: C2661: "No overloaded function takes 4 arguments"
And the second: C2440: "initializing, cannot convert from initializer list to Container, no constructor could take the source type, or constructor overload resolution was ambiguous."
Why not simply use a std::initialize_list:?
#include <iostream>
#include <type_traits>
#include <vector>
template <class T>
struct Foo
{
std::vector<T> data_;
explicit Foo(std::initializer_list<T> data) : data_(data)
{
std::cout << "1";
};
template <typename... Ts,
typename ENABLE=std::enable_if_t<(std::is_same_v<T,Ts> && ...)> >
explicit Foo(Ts... ts) : Foo(std::initializer_list<T>{ts...})
{
std::cout << "2";
}
};
int main()
{
Foo<int> f1{1, 3, 5, 7}; // prints 1
Foo<int> f2(1, 3, 5, 7); // prints 1 then 2
return 0;
}
If some Ts are different from T you will get a compile-time error.
With
gcc -std=c++17 prog.cpp
you get:
Foo<int> f1{1, 3, 5., 7};
error: narrowing conversion of ‘5.0e+0’ from ‘double’ to ‘int’ inside
{ } [-Wnarrowing] Foo f1{1, 3, 5., 7};
^
and
Foo<int> f2(1, 3, 5., 7);
you get
error: no matching function for call to ‘Foo::Foo(int, int,
double, int)’ Foo f2(1, 3, 5., 7);
^ note: candidate: ‘template Foo::Foo(Ts ...)’ explicit Foo(Ts... ts) :
Foo(std::initializer_list{ts...})
...
Update: if you really want to use something like raw pointer, here is a complete working example:
#include <iostream>
#include <memory>
#include <type_traits>
#include <vector>
template <class T>
struct Foo
{
size_t n_;
std::unique_ptr<T[]> data_;
explicit Foo(std::initializer_list<T> data) : n_(data.size()), data_(new T[n_])
{
std::copy(data.begin(), data.end(), data_.get());
std::cout << "1";
};
template <typename... Ts, typename ENABLE = std::enable_if_t<(std::is_same_v<T, Ts> && ...)> >
explicit Foo(Ts... ts) : Foo(std::initializer_list<T>{ts...})
{
std::cout << "2";
}
friend std::ostream& operator<<(std::ostream& out, const Foo<T>& toPrint)
{
for (size_t i = 0; i < toPrint.n_; i++)
std::cout << "\n" << toPrint.data_[i];
return out;
}
};
int main()
{
Foo<int> f1{1, 3, 5, 7}; // prints 1
Foo<int> f2(1, 3, 5, 7); // prints 1,2
std::cout << f1;
std::cout << f2;
return 0;
}
I let you replace unique_ptr by a raw pointer with all the extra work: delete[] etc...
std::is_same only compares two types, and you can't use pack expansions to declare multiple template parameters. That means you'll need to pull all of your std::is_same checks out into another check:
template <typename T, typename... Ts>
struct all_same : std::bool_constant<(std::is_same<T, Ts>::value && ...)> {};
template <typename T>
struct Foo
{
std::vector<T> data_;
template <typename... Ts, std::enable_if_t<all_same<T, std::decay_t<Ts>...>::value>* = nullptr>
Foo(Ts&&... ts)
: data_{std::forward<Ts>(ts)...}
{
}
};
Live Demo
You also need to allocate memory for your data_ array. Here I've used std::vector to take care of that allocation for me, but you could use new[] and delete[] to manage it yourself if you really want to.
enable_if and is_same won't store anything anywhere, they are only compile-time constructs and do not yield to any code in the binary executable.
Regardless of the syntax, what your code is essentially doing is trying to take the address of a constructor argument (which is a temporary). This will be a dangling pointer as soon as the constructor exits.
Either Foo owns the memory area and must allocate in constructor and delete in destructor (if any doubt: use std::vector!), or it aliases some external memory, and must receive a pointer to that memory.
Now regarding syntax:
std::is_same is a template that provides a value boolean constant and is to be used like so: std::is_same<T1, T2>::value. Alternatively you can use std::is_same_v<T1, T2>.
std::enable_if provides a type type member, only if the constant expression (1st template parameter) is true. Use it like std::enable_if<expr, T>::type. If expr is true, type is a typedef to T. Otherwise it is not defined and yields a substitution failure. Alternatively you can use std::enable_if_t<expr, T>
You can have a look here for a similar approach of yours.
But you can also simplify all this by using a member vector. In that case, the vector constructor ensures that all arguments have compatible types. Here is a complete example:
#include <string>
#include <vector>
#include <iostream>
#include <type_traits>
using namespace std;
template<typename T>
struct Foo {
vector<T> data_;
template<typename ...Ts>
explicit Foo(Ts... ts) : data_{ ts... } {}
void print() {
for (const auto &v : data_) {
cout << v << " ";
}
cout << endl;
}
};
int main() {
Foo<int> ints { 1, 2, 3, 4, 5 };
Foo<string> strings { "a", "b", "c", "d", "e"};
// Foo<string> incorrect { "a", 2, "c", 4, "e"};
ints.print();
strings.print();
// incorrect.print();
return 0;
}
The most idiomatic way to do this in C++17 is using std::cunjunction_v. It lazily evaluates subsequent values and allows you to avoid fold expressions. Also messages generated by the compiler are the same for both of casese you mentioned in edited piece of code.
Also, passing data by const-rvalue-ref makes no sense, since it is impossible to move data from const objects. Additionaly you were moving pack of arguments to a pointer. I didn't know what to do about it so just removed it.
Additionaly, take a look at the std::decay_t - without it it would not work, as sometimes Ts is deduced not as int but as const int & or int &. This leads to std::is_same_v being false.
The code below compiles fine at godbolt:
template<typename T>
struct Foo {
T* data_;
template<
typename... Ts,
std::enable_if_t<
std::conjunction_v<
std::is_same<T, std::decay_t<Ts>>...
>
> * = nullptr
>
explicit Foo( Ts&&... ts ) : data_{ } {}
};
(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.
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)... };
}
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.
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});
EDIT No longer relevant:
In fact this make_array implementation is listed in sizeof... operator
The code below introduces undefined behavior as per [namespace.std]/4.4
4.4 The behavior of a C++ program is undefined if it declares a deduction guide for any standard library class template.
# c++17 version
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
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:
return std::array<Type, …>{values};
we should do:
return {{values}};
2. Make make_array a constexpr function
This 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,
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_asserting 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 ints)).
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:
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<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, "!");
C++11 will support this manner of initialization for (most?) std containers.
(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;
}
С++17 compact implementation.
template <typename... T>
constexpr auto array_of(T&&... t) {
return std::array{ static_cast<std::common_type_t<T...>>(t)... };
}
While this answer is directed more towards this question, that question was marked as a duplicate of this question. Hence, this answer is posted here.
A particular use that I feel hasn't been fully covered is a situation where you want to obtain a std::array of chars initialized with a rather long string literal but don't want to blow up the enclosing function. There are a couple of ways to go about this.
The following works but requires us to explicitly specify the size of the string literal. This is what we're trying to avoid:
auto const arr = std::array<char const, 12>{"some string"};
One might expect the following to produce the desired result:
auto const arr = std::array{"some string"};
No need to explicitly specify the size of the array during initialization due to template deduction. However, this wont work because arr is now of type std::array<const char*, 1>.
A neat way to go about this is to simply write a new deduction guide for std::array. But keep in mind that some other code could depend on the default behavior of the std::array deduction guide.
namespace std {
template<typename T, auto N>
array(T (&)[N]) -> array<T, N>;
}
With this deduction guide std::array{"some string"}; will be of type std::array<const char, 12>. It is now possible to initialize arr with a string literal that is defined somewhere else without having to specify its size:
namespace {
constexpr auto some_string = std::array{"some string"};
}
auto func() {
auto const arr = some_string;
// ...
}
Alright, but what if we need a modifiable buffer and we want to initialize it with a string literal without specifying its size?
A hacky solution would be to simply apply the std::remove_cv type trait to our new deduction guide. This is not recommended because this will lead to rather surprising results. String literals are of type const char[], so it's expected that our deduction guide attempts to match that.
It seems that a helper function is necessary in this case. With the use of the constexpr specifier, the following function can be executed at compile time:
#include <array>
#include <type_traits>
template<typename T, auto N>
constexpr auto make_buffer(T (&src)[N]) noexcept {
auto tmp = std::array<std::remove_cv_t<T>, N>{};
for (auto idx = decltype(N){}; idx < N; ++idx) {
tmp[idx] = src[idx];
}
return tmp;
}
Making it possible to initialize modifiable std::array-like buffers as such:
namespace {
constexpr auto some_string = make_buffer("some string");
}
auto func() {
auto buff = some_string;
// ...
}
And with C++20, the helper function can even be simplified:
#include <algorithm>
#include <array>
#include <type_traits>
template<typename T, auto N>
constexpr auto make_buffer(T (&src)[N]) noexcept {
std::array<std::remove_cv_t<T>, N> tmp;
std::copy(std::begin(src), std::end(src), std::begin(tmp));
return tmp;
}
C++20 UPDATE: Although there are some excellent answers that provide the desired functionality (such as Gabriel Garcia's answer that uses std::index_sequence), I am adding this answer because the simplest way to do this as of C++20 isn't mentioned: just use std::to_array(). Using the OP's last example of an array of structs:
struct A{ int a; int b; };
// C syntax
A arr[] = { {1,2}, {3,4} };
// using std::array
auto std_array = std::to_array<A>({ {1,2}, {3,4} });
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.
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::arrays transparently.
None of the template approaches worked properly for me for arrays of structs, so I crafted this macro solution:
#define make_array(T, ...) \
(std::array<T,sizeof((T[]){ __VA_ARGS__ })/sizeof(T)> {{ __VA_ARGS__ }})
auto a = make_array(int, 1, 2, 3);
struct Foo { int x, y; };
auto b = make_array(Foo,
{ 1, 2 },
{ 3, 4 },
{ 5, 6 },
);
Note that although the macro expands its array arguments twice, the first time is inside sizeof, so any side effects in the expression will correctly happen only once.
Have fun!
(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.
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)... };
}
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.
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});
EDIT No longer relevant:
In fact this make_array implementation is listed in sizeof... operator
The code below introduces undefined behavior as per [namespace.std]/4.4
4.4 The behavior of a C++ program is undefined if it declares a deduction guide for any standard library class template.
# c++17 version
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
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:
return std::array<Type, …>{values};
we should do:
return {{values}};
2. Make make_array a constexpr function
This 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,
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_asserting 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 ints)).
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:
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<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, "!");
C++11 will support this manner of initialization for (most?) std containers.
(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;
}
С++17 compact implementation.
template <typename... T>
constexpr auto array_of(T&&... t) {
return std::array{ static_cast<std::common_type_t<T...>>(t)... };
}
While this answer is directed more towards this question, that question was marked as a duplicate of this question. Hence, this answer is posted here.
A particular use that I feel hasn't been fully covered is a situation where you want to obtain a std::array of chars initialized with a rather long string literal but don't want to blow up the enclosing function. There are a couple of ways to go about this.
The following works but requires us to explicitly specify the size of the string literal. This is what we're trying to avoid:
auto const arr = std::array<char const, 12>{"some string"};
One might expect the following to produce the desired result:
auto const arr = std::array{"some string"};
No need to explicitly specify the size of the array during initialization due to template deduction. However, this wont work because arr is now of type std::array<const char*, 1>.
A neat way to go about this is to simply write a new deduction guide for std::array. But keep in mind that some other code could depend on the default behavior of the std::array deduction guide.
namespace std {
template<typename T, auto N>
array(T (&)[N]) -> array<T, N>;
}
With this deduction guide std::array{"some string"}; will be of type std::array<const char, 12>. It is now possible to initialize arr with a string literal that is defined somewhere else without having to specify its size:
namespace {
constexpr auto some_string = std::array{"some string"};
}
auto func() {
auto const arr = some_string;
// ...
}
Alright, but what if we need a modifiable buffer and we want to initialize it with a string literal without specifying its size?
A hacky solution would be to simply apply the std::remove_cv type trait to our new deduction guide. This is not recommended because this will lead to rather surprising results. String literals are of type const char[], so it's expected that our deduction guide attempts to match that.
It seems that a helper function is necessary in this case. With the use of the constexpr specifier, the following function can be executed at compile time:
#include <array>
#include <type_traits>
template<typename T, auto N>
constexpr auto make_buffer(T (&src)[N]) noexcept {
auto tmp = std::array<std::remove_cv_t<T>, N>{};
for (auto idx = decltype(N){}; idx < N; ++idx) {
tmp[idx] = src[idx];
}
return tmp;
}
Making it possible to initialize modifiable std::array-like buffers as such:
namespace {
constexpr auto some_string = make_buffer("some string");
}
auto func() {
auto buff = some_string;
// ...
}
And with C++20, the helper function can even be simplified:
#include <algorithm>
#include <array>
#include <type_traits>
template<typename T, auto N>
constexpr auto make_buffer(T (&src)[N]) noexcept {
std::array<std::remove_cv_t<T>, N> tmp;
std::copy(std::begin(src), std::end(src), std::begin(tmp));
return tmp;
}
C++20 UPDATE: Although there are some excellent answers that provide the desired functionality (such as Gabriel Garcia's answer that uses std::index_sequence), I am adding this answer because the simplest way to do this as of C++20 isn't mentioned: just use std::to_array(). Using the OP's last example of an array of structs:
struct A{ int a; int b; };
// C syntax
A arr[] = { {1,2}, {3,4} };
// using std::array
auto std_array = std::to_array<A>({ {1,2}, {3,4} });
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.
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::arrays transparently.
None of the template approaches worked properly for me for arrays of structs, so I crafted this macro solution:
#define make_array(T, ...) \
(std::array<T,sizeof((T[]){ __VA_ARGS__ })/sizeof(T)> {{ __VA_ARGS__ }})
auto a = make_array(int, 1, 2, 3);
struct Foo { int x, y; };
auto b = make_array(Foo,
{ 1, 2 },
{ 3, 4 },
{ 5, 6 },
);
Note that although the macro expands its array arguments twice, the first time is inside sizeof, so any side effects in the expression will correctly happen only once.
Have fun!