Say I have two classes:
template <unsigned N>
class Pixel {
float color[N];
public:
Pixel(const std::initializer_list<float> &il)
{
// Assume this code can create a Pixel object from exactly N floats, and would throw a compiler error otherwise
}
};
template <unsigned N>
class PixelContainer {
std::vector<Pixel<N>> container;
};
What I'm trying to do is to write a constructor for PixelContainer such that:
It would instantiate correctly for the following cases (example, not exhaustive):
PixelContainer<3> pc1(1, 2, 3) // Creates a container containing one Pixel<3> objects
PixelContainer<3> pc2(1, 2, 3, 4, 5, 6) // Creates a container containing 2 Pixel<3> objects
PixelContainer<2> pc3(1, 2, 3, 4, 5, 6) // Creates a container containing 3 Pixel<2> objects
It would not compile for the following cases (as example, not exhaustive):
PixelContainer<3> pc4(2, 3) // Not enough arguments
PixelContainer<2> pc5(1, 2, 3, 4, 5) // Too many arguments
How do I achieve the above using template meta-programming? I feel it should be achievable, but can't figure out how. Specifically, I do not want to be doing the grouping myself e.g
PixelContainer<2> pc2({1, 2}, {3, 4}, {5, 6}) // Creates a container containing 3 Pixel<2> objects
(See this question for the inspiration behind mine)
template<class T, std::size_t I, std::size_t...Offs, class Tup>
T create( std::index_sequence<Offs...>, Tup&& tup ) {
return T( std::get<I+Offs>(std::forward<Tup>(tup))... );
}
template <unsigned N>
struct Pixel {
float color[N];
template<class...Ts,
std::enable_if_t< sizeof...(Ts)==N, bool > = true
>
Pixel(Ts&&...ts):color{ std::forward<Ts>(ts)... } {};
};
template <unsigned N>
struct PixelContainer {
std::vector<Pixel<N>> container;
template<class T0, class...Ts,
std::enable_if_t<!std::is_same<std::decay_t<T0>, PixelContainer>{}, bool> =true
>
PixelContainer(T0&& t0, Ts&&...ts):
PixelContainer( std::make_index_sequence<(1+sizeof...(Ts))/N>{}, std::forward_as_tuple( std::forward<T0>(t0), std::forward<Ts>(ts)... ) )
{}
PixelContainer() = default;
private:
template<class...Ts, std::size_t...Is>
PixelContainer( std::index_sequence<Is...>, std::tuple<Ts&&...>&& ts ):
container{ create<Pixel<N>, Is*N>( std::make_index_sequence<N>{}, std::move(ts) )... }
{}
};
create takes a type, a starting index, and a index sequence of offsets. Then it takes a tuple.
It then creates the type from the (starting index)+(each of the offsets) and returns it.
We use this in the private ctor of PixelContainer. It has an index sequence element for each of the Pixels whose elements are in the tuple.
We multiply the index sequence element by N, the number of elements per index sequence, and pass that to create. Also, we pass in an index sequence of 0,...,N-1 for the offsets, and the master tuple.
We then unpack that into a {} enclosed ctor for container.
The public ctor just forwards to the private ctor with the right pack of indexes of one-per-element (equal to argument count/N). It has some SFINAE annoyance enable_if_t stuff to avoid it swallowing stuff that should go to a copy ctor.
Live example.
Also,
std::enable_if_t<0 == ((sizeof...(Ts)+1)%N), bool> =true
could be a useful SFINAE addition to PixelContainer's public ctor.
Without it, we simply round down and discard "extra" elements passed to PixelContainer. With it, we get a "no ctor found" if we have extra elements (ie, not a multiple of N).
Made something as well, which relies more on compiler optimizations for performance than #Yakk's answer.
It uses temporary std::arrays. temp is used to store the passed values somewhere. temp_pixels is used to copy pixel data from temp. Finally temp is copied into container.
I believe that those arrays do get optimized away, but I'm not certain. Looking at godbolt it seems that they are but I am not good at reading compiler assembly output :)
#include <array>
#include <algorithm>
#include <cstddef>
#include <vector>
template <unsigned N>
struct Pixel {
float color[N]; // consider std::array here
};
template <unsigned N>
class PixelContainer {
std::vector<Pixel<N>> container;
public:
template<class... Ts>
PixelContainer(Ts... values)
{
static_assert(sizeof...(Ts) % N == 0, "Pixels should be grouped by 3 values in PixelContainer constructor");
const std::array<float, sizeof...(Ts)> temp{float(values)...};
std::array<Pixel<N>, sizeof...(Ts) / N> temp_pixels{};
for (std::size_t i = 0; i < sizeof...(Ts); i += N)
{
auto& pixel = temp_pixels[i / N];
std::copy(
temp.begin() + i, temp.begin() + i + N,
pixel.color
);
}
container = std::vector<Pixel<N>>(temp_pixels.begin(), temp_pixels.end());
}
};
int main()
{
PixelContainer<3> pc1(1, 2, 3); // Creates a container containing one Pixel<3> objects
PixelContainer<3> pc2(1, 2, 3, 4, 5, 6); // Creates a container containing 2 Pixel<3> objects
PixelContainer<2> pc3(1, 2, 3, 4, 5, 6); // Creates a container containing 3 Pixel<2> objects
/*
PixelContainer<3> pc4(2, 3); // Not enough arguments
PixelContainer<2> pc5(1, 2, 3, 4, 5); // Too many arguments
*/
}
I would propose some hybrid version, there is still a temporary array, but no temporary for pixels
template <unsigned N>
struct PixelContainer {
template<std::size_t S, std::size_t... elts>
auto createOne(const std::array<float, S> &arr, std::size_t offset,
std::index_sequence<elts...>) {
return Pixel<N>{ arr[offset + elts]... };
}
template<typename... Floats>
PixelContainer(Floats... vals) {
static_assert(sizeof...(vals) % N == 0, "invalid number of args");
std::array<float, sizeof...(vals)> arr = { float(vals)... };
for (size_t i = 0; i < sizeof...(vals) / N; i++) {
container.push_back(createOne(arr, i * N, std::make_index_sequence<N>{}));
}
}
std::vector<Pixel<N>> container;
};
I think the answer is pretty much given in the link you provided (C++ number of function's parameters fixed by template parameter). You just need to change the assert there: instead of sizeof...(Floats) == N you'll want sizeof...(Floats) % N == 0.
Related
Say I am declaring a matrix like this--> long long int A[100][100]. Is there any way to assign the value 1 to all its elements at the time of declaring the matrix?
You can use the fill_n method and initialize array like this:
std::fill_n(&A[0][0], sizeof(A) / sizeof(**A), 1);
Check this for more information about fill_na.
Hope it solves your question.
Is there any way to assign the value 1 to all its elements at the time of declaring the matrix?
I interpret that as you want something constexpr and I'd use std::array instead of plain arrays since std::arrays are easier to work with.
I've made templates for make_array that creates a 1 dimensional array and templates for make_2d_array that creates a 2 dimensional array, using the first set of templates.
The goal of the templates is that if we for example want a 2d array with 2 rows and 3 columns of int with all ints initialized with 1, this should be created:
std::array<std::array<int, 3>, 2> A{{
{1, 1, 1},
{1, 1, 1}
}};
In the code below make_array<int, 3, 1>() will create the inner arrays, std::array<int, 3>{1, 1, 1}, and make_2d_array<int, 2, 3, 1>() will be equivalent to:
std::array<std::array<int, 3>, 2> A{
make_array<int, 3, 1>(), // std::array<int, 3>{1, 1, 1}
make_array<int, 3, 1>() // std::array<int, 3>{1, 1, 1}
};
These are a set of templates that can accomplish that:
#include <array>
#include <type_traits>
#include <utility>
//-------------------------------------------------------------
template <typename T, T init, std::size_t... X>
constexpr std::array<T, sizeof...(X)>
make_array(std::index_sequence<X...>) {
//
// used for folding only then discarded below
// |
return std::array<T, sizeof...(X)>{ ((void)(X), (init))... };
}
template <typename T, std::size_t X, T init = T{}>
constexpr std::array<T, X> make_array() {
return make_array<T, init>(std::make_index_sequence<X>{});
}
//-------------------------------------------------------------
template <typename T, T init, std::size_t X, std::size_t... Y>
constexpr std::array< std::array<T, X>, sizeof...(Y)>
make_2d_array(std::index_sequence<Y...>) {
return std::array< std::array<T, X>, sizeof...(Y)>{
//
// Y used for folding only and discarded
// |
((void)(Y), make_array<T, X, init>())...
};
}
template <typename T, std::size_t Y, std::size_t X, T init = T{}>
constexpr std::array<std::array<T, X>, Y>
make_2d_array() {
return make_2d_array<T, init, X>(std::make_index_sequence<Y>{});
}
It can then be used like this:
constexpr auto A = make_2d_array<int, 2, 3, 1>(); // Y=2, X=3, init value 1
Full demo
(click on the Cppinsights link top left in the demo if you'd like to see how the folding unfolds)
As shown in Ted Lyngmo's answer, instead of a long long int A[100][100] we can declare and initialize a std::array<std::array<long long, 100>, 100> using a constexpr function.
Since C++20, it's possible to declare a function with a consteval specifier which
specifies that a function is an immediate function, that is, every call to the function must produce a compile-time constant.
Moreover, the algorithm std::fill_n has been made constexpr.
So that we can write the following two function templates:
#include <algorithm>
#include <array>
template <class Type, std::size_t Size>
consteval auto make_1d_array(Type value)
{
std::array<Type, Size> result;
std::fill_n(result.begin(), Size, value);
return result;
}
template <class Type, std::size_t Rows, std::size_t Cols>
consteval auto make_2d_array(Type value)
{
std::array<std::array<Type, Cols>, Rows> result;
std::fill_n( result.begin(), Rows
, make_1d_array<Type, Cols>(value) );
return result;
}
Live demo.
I want to efficiently multiply the arguments from a parameter pack with the elements of a std::array:
int index(auto... Is, std::array<int,sizeof...(Is)> strides)
{
// pseudo-code
// int idx = 0;
// for(int i = 0; i < sizeof...(Is); ++i)
// idx += Is[i] * strides[i];
// return idx;
}
I can't quite wrap my brain around this one. I started down the road of an index sequence, but I could figure out how to incorporate the summation.
I am using c++17, so fold expressions are fair game if they would simplify the code.
Thanks for any pointers.
EDIT: Clarified the pseudo-code. The only pseudo part is the expression Is[i] which refers to the i'th parameter pack argument.
T.C.'s answer below was perfect and here is my final code which is a member function:
unsigned int index(auto... indexes)
{
unsigned int idx = 0, i = 0;
(..., (idx += indexes * m_strides[i++]));
return idx;
}
As of this writing, the code compiles using gcc 6.3.0 with the -fconcepts flag, which brings in the Concept TS.
Using auto... indexes is shorthand for template<typename Args> f(Args... indexes). I tried to use an unsigned int concept for the arguments, but I couldn't get that to work.
The (...,) fold is the key element and expands to something like (if you could actually [] into the parameter pack):
idx += indexes[0] * m_strides[i++], idx += indexes[1] * m_strides[i++], etc.
That was the insight I was missing.
I can't get auto... to work, so I changed the signature of index.
You will need an auxiliary function (index_helper here) to use index_sequence, since it relies on template argument deduction to fill in the indices.
#include <array>
#include <cstdio>
template <typename... T, size_t... i>
// ^~~~~~~~~~~
// use deduction to make {i...} = {0, 1, 2, ..., n}
static int index_helper(const std::array<int, sizeof...(T)>& strides,
std::index_sequence<i...>,
T... Is)
{
return (0 + ... + (strides[i] * Is));
}
template <typename... T>
int index(const std::array<int, sizeof...(T)>& strides, T... Is) {
return index_helper(strides, std::make_index_sequence<sizeof...(T)>(), Is...);
// ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// generates {0, 1, 2, ..., n}
}
int main() {
printf("%d\n", index({1, 100, 100000, 1000}, 2, 3, 5, 7));
// 507302
}
If you can hammer down the argument pack into one single type that is cheap to copy/move, you can just make it into an array:
T arr[] = { static_cast<T>(Is)... }; // for some T, possibly common_type_t<decltype(Is)...>
Then you can just turn your pseudocode into real code.
If that's not feasible, a comma fold can be used:
int idx = 0, i = 0;
(..., (idx += Is * strides[i++]));
return idx;
If I theoretically have a sequence of integers like
std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>
How can I filter it with some compile-time predicate to get a potentially smaller std::integer_sequence<int, ...>?
For the sake of argument, let's say that I want only the even values,
which leads to the question of "How can I make the following static_assert (or something close) pass?"
static_assert(std::is_same_v<std::integer_sequence<int, 0, 2, 4, 6, 8>,
decltype(FilterEvens(std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>{}))>,
"Integer sequences should be equal");
This question was inspired by thinking about how we might accomplish removing duplicates between two bitsets (this question), assuming we could represent the bitsets as integer_sequences containing only 0 and 1. Bonus points if you can solve that one in this manner, too
Filtering a sequence is equivalent to transforming a sequence of values into a sequence of sequences of at most one value and then concatenating them. That is, filtering the even values from <0,1,2,3> would be the same as transforming that into the sequence <<0>,<>,<2>,<>> and concatenating to yield <0,2>.
With C++17, this takes remarkably little code. We'll start with our own value and sequence type (you can easily convert a std::integer_sequence to a value_sequence):
template <auto >
struct value { };
template <auto... Vals>
struct value_sequence { };
The reason we use our own is so we can add operators to it. Like +:
template <auto... As, auto... Bs>
constexpr value_sequence<As..., Bs...> operator+(value_sequence<As...>,
value_sequence<Bs...> )
{
return {};
}
We'll use that for concatenation. Next, we add a function to transform a single value into a sequence of zero or one element:
template <auto Val, class F>
constexpr auto filter_single(value<Val>, F predicate) {
if constexpr (predicate(Val)) {
return value_sequence<Val>{};
}
else {
return value_sequence<>{};
}
}
And lastly, we just need our top-level filter to put it all together:
template <auto... Vals, class F>
constexpr auto filter(value_sequence<Vals...>, F predicate) {
return (filter_single(value<Vals>{}, predicate) + ...);
}
Usage from the original example:
constexpr auto evens = filter(
value_sequence<0, 1, 2, 3, 4, 5, 6, 7, 8, 9>{},
[](int i) constexpr { return i%2 == 0; });
How cool is C++17!
Edit 2
After some back and forth on Barry's answer, I've come up with the following answer that merges the concepts and handles some empty-sequence edge cases (Full code):
We are allowed to pass a predicate to a function only if it is a constexpr lambda, as only literal types are allowed in constexpr functions, and normal free-floating functions aren't literal types (although I suppose you could wrap one within your lambda).
Our generic filter function will accept a sequence and a predicate, and return a new sequence. We will use constexpr if to handle empty sequence cases (which also requires the maybe_unused attribute on the predicate, because it's unused) :
template<class INT, INT... b, class Predicate>
constexpr auto Filter(std::integer_sequence<INT, b...>, [[maybe_unused]] Predicate pred)
{
if constexpr (sizeof...(b) > 0) // non empty sequence
return concat_sequences(FilterSingle(std::integer_sequence<INT, b>{}, pred)...);
else // empty sequence case
return std::integer_sequence<INT>{};
}
The Filter function calls FilterSingle for each element in the provided sequence, and concatenates the result of all of them:
template<class INT, INT a, class Predicate>
constexpr auto FilterSingle(std::integer_sequence<INT, a>, Predicate pred)
{
if constexpr (pred(a))
return std::integer_sequence<INT, a>{};
else
return std::integer_sequence<INT>{};
}
To concatenate sequences, the basic approach is thus:
template<typename INT, INT... s, INT... t>
constexpr std::integer_sequence<INT,s...,t...>
concat_sequences(std::integer_sequence<INT, s...>, std::integer_sequence<INT, t...>){
return {};
}
Although because of template expansion we'll have more than 2 sequences a lot of time, so we need a recursive case:
template<typename INT, INT... s, INT... t, class... R>
constexpr auto
concat_sequences(std::integer_sequence<INT, s...>, std::integer_sequence<INT, t...>, R...){
return concat_sequences(std::integer_sequence<INT,s...,t...>{}, R{}...);
}
And since we may try to concatenate an empty sequence with nothing (can happen if no elements pass the filter), we need another base case:
template<typename INT>
constexpr std::integer_sequence<INT>
concat_sequences(std::integer_sequence<INT>){
return {};
}
Now, for our predicate we will use a constexpr lambda. Note that we do not need to specify it as constexpr explicitly because it already satisfies the criteria to automatically become constexpr
auto is_even = [](int _in) {return _in % 2 == 0;};
So our full test looks like this:
auto is_even = [](int _in) {return _in % 2 == 0;};
using expected_type = std::integer_sequence<int, 0, 2, 4, 6, 8>;
using test_type = std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>;
constexpr auto result = Filter(test_type{}, is_even);
using result_type = std::decay_t<decltype(result)>;
static_assert(std::is_same_v<expected_type, result_type>, "Integer sequences should be equal");
Previous approach
My approach is repeatedly construct and concatenate sub-sequences, where the base case (sequence of one) will either return an empty sequence or the same sequence if the predicate is satisfied.
For writing the predicate, I'll take advantage of C++17's constexpr if for defining a predicate.
Predicate:
// base case; empty sequence
template<class INT>
constexpr auto FilterEvens(std::integer_sequence<INT>)
{
return std::integer_sequence<INT>{};
}
// base case; one element in the sequence
template<class INT, INT a>
constexpr auto FilterEvens(std::integer_sequence<INT, a>)
{
if constexpr (a % 2 == 0)
return std::integer_sequence<INT, a>{};
else
return std::integer_sequence<INT>{};
}
// recursive case
template<class INT, INT a, INT... b>
constexpr auto FilterEvens(std::integer_sequence<INT, a, b...>)
{
return concat_sequence(FilterEvens(std::integer_sequence<INT, a>{}),
FilterEvens(std::integer_sequence<INT, b...>{}));
}
Concatenation logic:
template <typename INT, INT ...s, INT ...t>
constexpr auto
concat_sequence(std::integer_sequence<INT,s...>,std::integer_sequence<INT,t...>){
return std::integer_sequence<INT,s...,t...>{};
}
And the test:
int main()
{
static_assert(std::is_same_v<std::integer_sequence<int, 0, 2, 4, 6, 8>, decltype(FilterEvens(std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>{}))>, "Integer sequences should be equal");
}
Live Demo
Edit:
I used this approach to solve the "Bonus" question for removing matched bits here: https://stackoverflow.com/a/41727221/27678
An alternative solution leveraging tuples:
template <auto Pred, class Type, Type... I>
struct filter_integer_sequence_impl
{
template <class... T>
static constexpr auto Unpack(std::tuple<T...>)
{
return std::integer_sequence<Type, T::value...>();
}
template <Type Val>
using Keep = std::tuple<std::integral_constant<Type, Val>>;
using Ignore = std::tuple<>;
using Tuple = decltype(std::tuple_cat(std::conditional_t<(*Pred)(I), Keep<I>, Ignore>()...));
using Result = decltype(Unpack(Tuple()));
};
template <auto Pred, class Type, Type... I>
constexpr auto filter_integer_sequence(std::integer_sequence<Type, I...>)
{
return typename filter_integer_sequence_impl<Pred, Type, I...>::Result();
}
template <class Pred, class Type, Type... I>
constexpr auto filter_integer_sequence(std::integer_sequence<Type, I...> sequence, Pred)
{
return filter_integer_sequence<(Pred*)nullptr>(sequence);
}
Used like this:
constexpr auto predicate = [](int val) { return (val % 2) == 0; };
constexpr auto start = std::integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>();
constexpr auto filtered = filter_integer_sequence(start, predicate);
constexpr auto expected = std::integer_sequence<int, 0, 2, 4, 6, 8>();
static_assert(std::is_same_v<decltype(filtered), decltype(expected)>);
The second overload of filter_integer_sequence is only necessary for C++17 which doesn't allow us to use lambdas for non-type template parameters. C++20 lifts that restriction, so in that case only the first overload is needed. Note that the first overload is still necessary in C++17 for handling regular function pointers.
Like the other solutions here, the second overload only works for non-capturing lambdas. This is why we can safely evaluate (*(Pred*)nullptr)(I) as constexpr since the lambda body doesn't actually use the this pointer.
Can I initialize an array using the std::initializer_list object instead of brace-enclosed initializer?
As known, we can do this: http://en.cppreference.com/w/cpp/language/aggregate_initialization
unsigned char b[5]{"abc"};
// equivalent to unsigned char b[5] = {'a', 'b', 'c', '\0', '\0'};
int ar[] = {1,2,3};
std::array<int, 3> std_ar2{ {1,2,3} }; // std::array is an aggregate
std::array<int, 3> std_ar1 = {1, 2, 3};
But I can't initialize an array by std::initializer_list il;:
http://ideone.com/f6aflX
#include <iostream>
#include <initializer_list>
#include <array>
int main() {
int arr1[] = { 1, 2, 3 }; // OK
std::array<int, 3> arr2 = { 1, 2, 3 }; // OK
std::initializer_list<int> il = { 1, 2, 3 };
constexpr std::initializer_list<int> il_constexpr = { 1, 2, 3 };
//int arr3[] = il; // error
//int arr4[] = il_constexpr; // error
//std::array<int, 3> arr5 = il; // error
//std::array<int, 3> arr6 = il_constexpr; // error
return 0;
}
But how can I use std::initializer_list il; to initialize an array?
Other answered correctly said this is not possible upfront. But with little helpers, you can get pretty close
template<typename T, std::size_T N, std::size_t ...Ns>
std::array<T, N> make_array_impl(
std::initializer_list<T> t,
std::index_sequence<Ns...>)
{
return std::array<T, N>{ *(t.begin() + Ns) ... };
}
template<typename T, std::size_t N>
std::array<T, N> make_array(std::initializer_list<T> t) {
if(N > t.size())
throw std::out_of_range("that's crazy!");
return make_array_impl<T, N>(t, std::make_index_sequence<N>());
}
If you are open to more work arounds, you can put this into a class to catch statically-known length violations for the cases where you pass a braced init list. But be warned that most people who read this code will head-desk
template<typename T, std::size_t N>
struct ArrayInitializer {
template<typename U> struct id { using type = U; };
std::array<T, N> t;
template<typename U = std::initializer_list<T>>
ArrayInitializer(typename id<U>::type z)
:ArrayInitializer(z, std::make_index_sequence<N>())
{
if(N > z.size())
throw std::out_of_range("that's crazy!");
}
template<typename ...U>
ArrayInitializer(U &&... u)
:t{ std::forward<U>(u)... }
{ }
private:
template<std::size_t ...Ns>
ArrayInitializer(std::initializer_list<T>& t,
std::index_sequence<Ns...>)
:t{ *(t.begin() + Ns) ... }
{ }
};
template<typename T, std::size_t N>
std::array<T, N> f(ArrayInitializer<T, N> ai) {
return std::move(ai.t);
}
int main() {
f<int, 5>({1, 2, 3, 4, 5}); // OK
f<int, 5>({1, 2, 3, 4, 5, 6}); // "too many initializers for array<int, 5>"
std::initializer_list<int> il{1, 2, 3, 4, 5};
f<int, 5>(il); // ok
}
Note that both the non-static case at the top of the answer and the "head-desk" case do only check whether you provide too few initializing elements, and errors out then, for the initializer_list case. If you provide too many for the initializer_list case, the trailing elements are just ignored.
As far I know, no: you can't initialize a std::array with a std::initializer_list.
The problem is that std::array is intended as a lightweight replacement (a wrapper) for the classic C-style array. So light that is without constructors, so only implicit constructor can be used.
The construction with aggregate initialization (via implicit constructor) is possible because it's possible for the C-style array.
But std::initializer_list is a class, more complicated than an aggregate inizialization.
You can initialize, by example, a std::vector with a std::initializer_list but only because there is an explicit constructor, for std::vector, that receive a std::initializer_list. But std::vector is a heavier class.
The only solution that I see is a 2 step way: (1) construction and (2) copy of the std::initializer_list values. Something like
std::array<int, 3> arr5;
auto ui = 0U;
auto cit = il.cbegin();
while ( (ui < arr5.size()) && (cit != il.cend()) )
arr5[ui++] = *cit++;
p.s.: sorry for my bad English.
The problem with std::array is that it is required to be an aggregate type, hence it does not have constructors.
Hence only aggregate initialization or trivial copy are possible.
std::initializer_list is a class other than std::array, so a (missing) implicit conversion is required.
See http://en.cppreference.com/w/cpp/language/aggregate_initialization
and http://en.cppreference.com/w/cpp/container/array
for reference.
Short question:
Is there a shorter way to do this
array<array<atomic<int>,n>,m> matrix;
I was hoping for something like
array< atomic< int>,n,m> matrix;
but it doesnt work...
A template alias might help out:
#include <array>
template <class T, unsigned I, unsigned J>
using Matrix = std::array<std::array<T, J>, I>;
int main()
{
Matrix<int, 3, 4> matrix;
}
A palatable workaround for compilers that don't support template aliases yet is to use a simple metafunction to generate the type:
#include <cstddef>
#include <array>
template<class T, std::size_t RowsN, std::size_t ColumnsN>
struct Matrix
{
typedef std::array<std::array<T, ColumnsN>, RowsN> type; // row major
private:
Matrix(); // prevent accidental construction of the metafunction itself
};
int main()
{
Matrix<int, 3, 4>::type matrix;
}
Solution using variadic templates (slightly more complex than the template alias, but more general purpose)
template <typename T, std::size_t thisSize, std::size_t ... otherSizes>
class multi_array : private std::array<multi_array<T, otherSizes...>, thisSize>
{
using base_array = std::array<multi_array<T, otherSizes...>, thisSize>;
public:
using base_array::operator[];
// TODO: add more using statements to make methods
// visible. This is less typing (and less error-prone)
// than forwarding to the base_array type.
};
template <typename T, std::size_t thisSize>
class multi_array<T, thisSize> : private std::array<T, thisSize>
{
using base_array = std::array<T, thisSize>;
public:
using base_array::operator[];
// TODO: add more using statements to make methods
// visible. This is less typing (and less error-prone)
// than forwarding to the base_array type.
};
There might be some improvement on assigning to non-leaves of the array that could be made.
I tested with a relatively recent build of clang/LLVM.
Enjoy!
When nested, std::array can become very hard to read and unnecessarily verbose. The opposite ordering of the dimensions can be especially confusing.
For example:
std::array < std::array <int, 3 > , 5 > arr1;
compared to
char c_arr [5][3];
Also, note that begin(), end() and size() all return meaningless values when you nest std::array.
For these reasons I've created my own fixed size multidimensional array containers, array_2d and array_3d. They have the advantage that they work with C++98.
They are analogous to std::array but for multidimensional arrays of 2 and 3 dimensions. They are safer and have no worse performance than built-in multidimensional arrays. I didn't include a container for multidimensional arrays with dimensions greater than 3 as they are uncommon. In C++11 a variadic template version could be made which supports an arbitrary number of dimensions (Something like Michael Price's example).
An example of the two-dimensional variant:
//Create an array 3 x 5 (Notice the extra pair of braces)
fsma::array_2d <double, 3, 5> my2darr = {{
{ 32.19, 47.29, 31.99, 19.11, 11.19},
{ 11.29, 22.49, 33.47, 17.29, 5.01 },
{ 41.97, 22.09, 9.76, 22.55, 6.22 }
}};
Full documentation is available here:
http://fsma.googlecode.com/files/fsma.html
You can download the library here:
http://fsma.googlecode.com/files/fsma.zip
Here's a simple, generic version:
template <typename T, size_t d1, size_t d2, size_t... ds>
struct GetMultiDimArray
{
using type = std::array<typename GetMultiDimArray<T, d2, ds...>::type, d1>;
};
template <typename T, size_t d1, size_t d2>
struct GetMultiDimArray<T, d1, d2>
{
using type = std::array<std::array<T, d2>, d1>;
};
template <typename T, size_t d1, size_t d2, size_t... ds>
using MultiDimArray = typename GetMultiDimArray<T, d1, d2, ds...>::type;
// Usage:
MultiDimArray<int, 3, 2> arr {1, 2, 3, 4, 5, 6};
assert(arr[1][1] == 4);