Create N-element constexpr array in C++11
Asked Answered
C

9

87

Hello i'm learning C++11, I'm wondering how to make a constexpr 0 to n array, for example:

n = 5;

int array[] = {0 ... n};

so array may be {0, 1, 2, 3, 4, 5}

Carminecarmita answered 26/9, 2013 at 4:13 Comment(2)
Here's a recent similar question using std::arrays.Mitra
We're at C++17 now. So, @abyx 's answer is now the best answer, because it can no longer be considered to be dependent on "compiler extensions".Staphylococcus
M
73

In C++14 it can be easily done with a constexpr constructor and a loop:

#include <iostream>

template<int N>
struct A {
    constexpr A() : arr() {
        for (auto i = 0; i != N; ++i)
            arr[i] = i; 
    }
    int arr[N];
};

int main() {
    constexpr auto a = A<4>();
    for (auto x : a.arr)
        std::cout << x << '\n';
}
Marhtamari answered 25/12, 2015 at 18:48 Comment(7)
this is concise. Why is : arr() necessary?Federal
@Federal probably because you cannot have uninitialized fields in constexpr function (which is a constructor here). Just a guess.Marhtamari
How could you pass a function to A that would be applied to all array elements? When using template <int N, class Function> ... constexpr A(Function f) : arr() {... I can't figure out how to instantiate the struct.Liddell
Note: doesn't work with Visual Studio 15, might be supported in Visual Studio 2017.Propriety
I cant get this to compile. I am also using Visual Studio 15. Can someone confirm that it is valid in 2017+?Horripilate
This works fine for me with C++17 in Visual Studio 2019. Regarding a short experiment on godbolt, the example also compiles with the oldest msvc toolchain 19.14 available there, which seems to be shipped with Visual Studio 2017.Wavelet
Great answer and this work nicely for me. Just wondering - in the newer versions of C++ are there any new syntactical ways of doing this?Hurried
P
58

Unlike those answers in the comments to your question, you can do this without compiler extensions.

#include <iostream>

template<int N, int... Rest>
struct Array_impl {
    static constexpr auto& value = Array_impl<N - 1, N, Rest...>::value;
};

template<int... Rest>
struct Array_impl<0, Rest...> {
    static constexpr int value[] = { 0, Rest... };
};

template<int... Rest>
constexpr int Array_impl<0, Rest...>::value[];

template<int N>
struct Array {
    static_assert(N >= 0, "N must be at least 0");

    static constexpr auto& value = Array_impl<N>::value;

    Array() = delete;
    Array(const Array&) = delete;
    Array(Array&&) = delete;
};

int main() {
    std::cout << Array<4>::value[3]; // prints 3
}
Prichard answered 26/9, 2013 at 4:31 Comment(2)
@Prichard nice, but "only" works for int or other types that can appear as non-type template parameters (so not for e.g. double). See my answer for the general solution.Epps
Nice. But I think I would hide the recursive variadic template behind a non-variadic public interface, to avoid confusion if somebody tries Array<9,3,5>.Uncivil
E
40

Based on @Xeo's excellent idea, here is an approach that lets you fill an array of

  • constexpr std::array<T, N> a = { fun(0), fun(1), ..., fun(N-1) };
  • where T is any literal type (not just int or other valid non-type template parameter types), but also double, or std::complex (from C++14 onward)
  • where fun() is any constexpr function
  • which is supported by std::make_integer_sequence from C++14 onward, but easily implemented today with both g++ and Clang (see Live Example at the end of the answer)
  • I use @JonathanWakely 's implementation at GitHub (Boost License)

Here is the code

template<class Function, std::size_t... Indices>
constexpr auto make_array_helper(Function f, std::index_sequence<Indices...>) 
-> std::array<typename std::result_of<Function(std::size_t)>::type, sizeof...(Indices)> 
{
    return {{ f(Indices)... }};
}

template<int N, class Function>
constexpr auto make_array(Function f)
-> std::array<typename std::result_of<Function(std::size_t)>::type, N> 
{
    return make_array_helper(f, std::make_index_sequence<N>{});    
}

constexpr double fun(double x) { return x * x; }

int main() 
{
    constexpr auto N = 10;
    constexpr auto a = make_array<N>(fun);

    std::copy(std::begin(a), std::end(a), std::ostream_iterator<double>(std::cout, ", ")); 
}

Live Example

Epps answered 26/9, 2013 at 8:48 Comment(1)
where auto is any literal type Isn't it a std::array of literal types, in auto a = make_array<N>(fun);, equivalent to auto a = std::array<decltype(fun(0)), N>{fun(0), fun(1), ..};? Also, the example constexpr auto a = {1,2}; deduces a to be a std::initializer_list, which isn't yet required to be a literal type (-> no constexpr). (I know it's rather pedantic, but I was confused at first glance.)Mitra
M
6

Use C++14 integral_sequence, or its invariant index_sequence

#include <iostream>

template< int ... I > struct index_sequence{ 

    using type = index_sequence;
    using value_type = int;

    static constexpr std::size_t size()noexcept{ return sizeof...(I); }
};

// making index_sequence
template< class I1, class I2> struct concat;

template< int ...I, int ...J> 
struct concat< index_sequence<I...>, index_sequence<J...> > 
        :  index_sequence< I ... , ( J + sizeof...(I) )... > {};

template< int N > struct make_index_sequence_impl;

template< int N > 
using make_index_sequence = typename make_index_sequence_impl<N>::type;

template< > struct make_index_sequence_impl<0> : index_sequence<>{};
template< > struct make_index_sequence_impl<1> : index_sequence<0>{};

template< int N > struct make_index_sequence_impl 
     : concat< make_index_sequence<N/2>, make_index_sequence<N - N/2> > {};



// now, we can build our structure.   
template < class IS > struct mystruct_base;

template< int ... I >
struct mystruct_base< index_sequence< I ... > >
{

   static constexpr int array[]{I ... };
};

template< int ... I >
constexpr int mystruct_base< index_sequence<I...> >::array[] ;

template< int N > struct mystruct 
   : mystruct_base< make_index_sequence<N > > 
{};

int main()
{
    mystruct<20> ms;

    //print
    for(auto e : ms.array)
    {
        std::cout << e << ' ';
    }
    std::cout << std::endl;

    return 0;
}

output: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

UPDATE: You may use std::array:

template< int ... I >
static constexpr std::array< int, sizeof...(I) >  build_array( index_sequence<I...> ) noexcept 
{ 
   return std::array<int, sizeof...(I) > { I... };
}

int main()
{
    std::array<int, 20> ma = build_array( make_index_sequence<20>{} );

    for(auto e : ma) std::cout << e << ' ';
    std::cout << std::endl;
}
Mutazilite answered 26/9, 2013 at 6:51 Comment(2)
Clang + libc++ tip of tree support this. Pass -std=c++1y.Manumission
I use Clang 3.3 and GCC 4.8.1 with -std=c++11 options.Mutazilite
D
2

For std::array in C++17, constexpr function are also accepted Note that the var 'arr' must be initialized by constexpr required.
(initialize: same meaning with answer of @abyx)

#include <array>
constexpr std::array<int, 3> get_array()
{
    std::array<int, 3> arr{0};
    arr[0] = 9;
    return arr;
}
static_assert(get_array().size() == 3);
Diarthrosis answered 24/12, 2022 at 13:22 Comment(0)
R
2

With C++17 this can be done easily as std::array::begin is marked constexpr.

template<std::size_t N> std::array<int, N + 1> constexpr make_array()
{
    std::array<int, N + 1> tempArray{};
    int count = 0;
    for(int &elem:tempArray)
    {
        elem = count++;
    }
    return tempArray;
}
int main()
{
    
    //-------------------------------vv------>pass the size here 
    constexpr auto arr  = make_array<5>();
   
    //lets confirm if all objects have the expected value 
    for(const auto &elem: arr)
    {
        std::cout << elem << std::endl; //prints 1 2 3 4 5 with newline in between
    }
    
}

Demo

Retina answered 24/12, 2022 at 13:41 Comment(0)
V
1
#include <array>
#include <iostream>

template<int... N>
struct expand;

template<int... N>
struct expand<0, N...>
{
    constexpr static std::array<int, sizeof...(N) + 1> values = {{ 0, N... }};
};

template<int L, int... N> struct expand<L, N...> : expand<L-1, L, N...> {};

template<int... N>
constexpr std::array<int, sizeof...(N) + 1> expand<0, N...>::values;

int main()
{
    std::cout << expand<100>::values[9];
}
Vocalise answered 3/10, 2013 at 20:15 Comment(0)
D
-2

Using boost preprocessor, it's very simple:

 #include <cstdio>
 #include <cstddef>

 #include <boost/preprocessor/repeat.hpp>
 #include <boost/preprocessor/comma_if.hpp>

 #define IDENTITY(z,n,dummy)   BOOST_PP_COMMA_IF(n) n

 #define INITIALIZER_n(n)   { BOOST_PP_REPEAT(n,IDENTITY,~)  }

 int main(int argc, char* argv[])
 {
     int array[] = INITIALIZER_n(25);

     for(std::size_t i = 0; i < sizeof(array)/sizeof(array[0]); ++i)
        printf("%d ",array[i]);

     return 0;
 }

OUTPUT:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
Diversiform answered 5/1, 2014 at 11:3 Comment(1)
I think you could also use one of the ENUM macros, e.g. BOOST_PP_ENUM_PARAMS(25, BOOST_PP_EMPTY()), instead of the REPEAT+COMMA_IFMitra
A
-6

Consider boost::mpl::range_c<int, 0, N> instead.

Ambi answered 4/12, 2016 at 20:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.