Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11: Compile-time Array with Logarithmic Evaluation Depth

Tags:

c++

c++11

One way to implement a C++11 array that has its elements initialized by a function of their index calculated by the compiler and have the results stored in the data section (.rodata) of the application image is to use templates, partial specialization and constexpr as follows:

#include <iostream>
#include <array>
using namespace std;

constexpr int N = 1000000;
constexpr int f(int x) { return x*2; }

typedef array<int, N> A;

template<int... i> constexpr A fs() { return A{{ f(i)... }}; }

template<int...> struct S;

template<int... i> struct S<0,i...>
{ static constexpr A gs() { return fs<0,i...>(); } };

template<int i, int... j> struct S<i,j...>
{ static constexpr A gs() { return S<i-1,i,j...>::gs(); } };

constexpr auto X = S<N-1>::gs();

int main()
{
        cout << X[3] << endl;
}

This doesn't work for large values of N:

error: constexpr evaluation depth exceeds maximum of 512 

This is because of the head-tail style of recursive template evaluation, that has linear depth in terms of N.

Is there a way to do it such that the evaluation depth is logarithmic in terms of N, rather than linear? (and hence would avoid the default depth limit)

like image 927
Andrew Tomazos Avatar asked Oct 25 '12 15:10

Andrew Tomazos


People also ask

Can I calculate values at compile time?

Some programming languages allow calculation of values at compile time. Calculate 10! (ten factorial) at compile time. Print the result when the program is run. Discuss what limitations apply to compile-time calculations in your language. First example with the assembler equivalence pseudo instruction (EQU):

How to coding a compile-time array?

Coding a Compile-Time Array A compile-time array is specified using the essential array specifications plus the keyword CTDATA. In addition, on a definition specification you can specify:

What are compile-time calculations in C++?

Compile-time calculations in C++ look quite different from normal code. We can only use templates, type definitions and a subset of integer arithmetic. It is not possible to use iteration. C++ compile-time programs are similar to programs in pure functional programming languages, albeit with a peculiar syntax.

What is array in C?

Explain Compile time and Run time initialization in C programming? Let’s take the concept of arrays to about compile time and run time initialization − Array is a collection of items stored at contiguous memory locations and elements can access by using indices. In compile time initialization, user has to enter the details in the program itself.


3 Answers

In c++14 with general constexpression function needn't any sequence, make_sequence

static constexpr int f(int i) noexcept { return i * 2; }

template< int N, typename F >
static constexpr std::array<int,N> generate_array( F fo ) noexcept
{
     std::array< int, N > result{}; // init with zero
     int i = 0; 
     for( auto& x : result) x = fo(++i);

     return result;
}

// test
constexpr auto arr = generate_array<10>( f );

Only has one problem, there is no compiler which can compile it :)

like image 30
Khurshid Avatar answered Oct 17 '22 23:10

Khurshid


If what you're using in the code is a weird form of the indices trick, here's an implementation that has O(log N) instantiations:

// using aliases for cleaner syntax
template<class T> using Invoke = typename T::type;

template<unsigned...> struct seq{ using type = seq; };

template<class S1, class S2> struct concat;

template<unsigned... I1, unsigned... I2>
struct concat<seq<I1...>, seq<I2...>>
  : seq<I1..., (sizeof...(I1)+I2)...>{};

template<class S1, class S2>
using Concat = Invoke<concat<S1, S2>>;

template<unsigned N> struct gen_seq;
template<unsigned N> using GenSeq = Invoke<gen_seq<N>>;

template<unsigned N>
struct gen_seq : Concat<GenSeq<N/2>, GenSeq<N - N/2>>{};

template<> struct gen_seq<0> : seq<>{};
template<> struct gen_seq<1> : seq<0>{};

// example

template<unsigned... Is>
void f(seq<Is...>);

int main(){
  f(gen_seq<6>());
}

Live example.

like image 195
Xeo Avatar answered Oct 17 '22 22:10

Xeo


The only tail recursive template, that's causing the linear template instantiation depth, is the construction of the list of integers in the template parameter list of S.

This can be done in logarithmic instantiation depth, as you suggest:

template <int ... Ints> struct seq;

template <int Start, int End> 
struct range
{
  typedef concat_seq<range<Start, Start+(End-Start)/2>::type, range<Start+(End-Start)/2, End>::type>::type type;
};

template<int Singleton>
struct range<Singleton, Singleton+1>
{
  typedef seq<Singleton> type;
};

template <int NoSingleton>
struct range<NoSinleton, NoSingleton>
{
  typedef seq<> type;
};

Add a bunch of typenames where appropriate, implement concat_seq (easy), extract the argument list for fs from range<0, N>::type and there you go.

like image 1
jpalecek Avatar answered Oct 17 '22 22:10

jpalecek