43

Suppose I have some constexpr function f:

constexpr int f(int x) { ... }

And I have some const int N known at compile time:

Either

#define N ...;

or

const int N = ...;

as needed by your answer.

I want to have an int array X:

int X[N] = { f(0), f(1), f(2), ..., f(N-1) }

such that the function is evaluated at compile time, and the entries in X are calculated by the compiler and the results are placed in the static area of my application image exactly as if I had used integer literals in my X initializer list.

Is there some way I can write this? (For example with templates or macros and so on)

Best I have: (Thanks to Flexo)

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

constexpr int N = 10;
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;
}
Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • 1
    Will this ever need to be recomputed, or this is something that never changes (like, say, a list of Fibonacci numbers)? If this never changes, it's much better to do the generation in other code and copy-paste it there in the source. – R. Martinho Fernandes Aug 24 '12 at 11:20
  • @R.MartinhoFernandes: Not sure that manual maintenance of intermediate data is a good release engineering. It is better documentation to have the source of f present in the code, even though it makes the compiler do a little more work on every build. If N is very large I would argue better process is to have array in seperate .o file that only gets recompiled when f changes. – Andrew Tomazos Aug 24 '12 at 11:24
  • if const int N= ... can be replaced with #define N ..., then BOOST_PP_REPEAT may help – user396672 Aug 24 '12 at 11:25
  • 2
    @AndrewTomazos-Fathomling You will notice that I said *if this never changes*. Who maintains a list of Fibonacci numbers? – R. Martinho Fernandes Aug 24 '12 at 11:25
  • @Andrew Not necessarily manual, generated by a tool (as a build step maybe). But I agree, a C++ only solution would be interesting. – Konrad Rudolph Aug 24 '12 at 11:25
  • possible duplicate of http://stackoverflow.com/q/2978259/819272 – TemplateRex Aug 24 '12 at 11:28
  • 4
    @rhalbersma: I wouldn't want to close this as a dupe. The other one is from 2010, and `constexpr` was far less widely available back then than it is now. This could produce answers very different from the old one. – sbi Aug 24 '12 at 11:31
  • I have a feeling that the optimizer would do this, however you code it. – enobayram Aug 24 '12 at 11:37
  • @enobayram: Entirely eliding a runtime loop (not just unrolling) would be a fairly bold compiler optimization. I am not sure you are correct. – Andrew Tomazos Aug 24 '12 at 13:36
  • @AndrewTomazos-Fathomling After unrolling, wouldn't you think it would realize that the expressions are simply a series of pure functions called with compile time constants? It would probably try to inline those functions anyway, and see that they reduce to a simple constant value. The question is still interesting though. – enobayram Aug 24 '12 at 13:41
  • @AndrewTomazos-Fathomling: if the definition of `f` is available, then it could be done, though I agree on the *bold* part; let's imagine the function is Ackermann's... – Matthieu M. Aug 24 '12 at 13:42
  • @enobayram: Alright I'll test it and see. – Andrew Tomazos Aug 24 '12 at 13:42
  • 1
    @enobayram: Nope, it unrolls it but doesn't move it to the data section. You still have N movl instructions at runtime. (gcc-4.7 -O2) – Andrew Tomazos Aug 24 '12 at 13:46
  • @AndrewTomazos-Fathomling Very interesting indeed, how about Flexo's solution? – enobayram Aug 24 '12 at 13:56
  • 2
    @enobayram: Yes, the data is in the .rodata section as expected. – Andrew Tomazos Aug 24 '12 at 14:02

8 Answers8

30

There is a pure C++11 (no boost, no macros too) solution to this problem. Using the same trick as this answer we can build a sequence of numbers and unpack them to call f to construct a std::array:

#include <array>
#include <algorithm>
#include <iterator>
#include <iostream>

template<int ...>
struct seq { };

template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> { };

template<int ...S>
struct gens<0, S...> {
  typedef seq<S...> type;
};

constexpr int f(int n) {
  return n;
}

template <int N>
class array_thinger {
  typedef typename gens<N>::type list;

  template <int ...S>
  static constexpr std::array<int,N> make_arr(seq<S...>) {
    return std::array<int,N>{{f(S)...}};
  }
public:
  static constexpr std::array<int,N> arr = make_arr(list()); 
};

template <int N>
constexpr std::array<int,N> array_thinger<N>::arr;

int main() {
  std::copy(begin(array_thinger<10>::arr), end(array_thinger<10>::arr), 
            std::ostream_iterator<int>(std::cout, "\n"));
}

(Tested with g++ 4.7)

You could skip std::array entirely with a bit more work, but I think in this instance it's cleaner and simpler to just use std::array.

You can also do this recursively:

#include <array>
#include <functional>
#include <algorithm>
#include <iterator>
#include <iostream>

constexpr int f(int n) {
  return n;
}

template <int N, int ...Vals>
constexpr
typename std::enable_if<N==sizeof...(Vals),std::array<int, N>>::type
make() {
  return std::array<int,N>{{Vals...}};
}

template <int N, int ...Vals>
constexpr
typename std::enable_if<N!=sizeof...(Vals), std::array<int,N>>::type 
make() {
  return make<N, Vals..., f(sizeof...(Vals))>();  
}

int main() {
  const auto arr = make<10>();
  std::copy(begin(arr), end(arr), std::ostream_iterator<int>(std::cout, "\n"));
}

Which is arguably simpler.

Community
  • 1
  • 1
Flexo
  • 87,323
  • 22
  • 191
  • 272
  • This is pretty good, but I think it can be done with a few less moving parts. – Andrew Tomazos Aug 24 '12 at 11:49
  • @AndrewTomazos-Fathomling - possibly, but not easily. It all happens (or can happen) at compile time though so I'd just concentrate on hiding the mechanics personally. If you want to make some tweaks to it feel free to edit them in if you want. – Flexo Aug 24 '12 at 11:52
  • Is std::array actually a literal type that can be returned from a constexpr function? In other words: Is the initialization of the static member called arr really static or dynamic? – sellibitze Aug 24 '12 at 11:52
  • @sellibitze - a large part of the point of `std::array` is you can return it from a function (compared to just an array). It's marked `constexpr` where needed for such usage. – Flexo Aug 24 '12 at 11:56
  • @Flexo: see update 1. its not so different from what you are doing, and obviously is only partial. – Andrew Tomazos Aug 24 '12 at 12:01
  • @Flexo: see update 2: I think I haven't got the syntax all correct, but maybe there is something there. – Andrew Tomazos Aug 24 '12 at 12:11
  • @AndrewTomazos-Fathomling - your update inspired me to try another idea which also worked :), It's very similar to your second update, but the enable_if is needed to hide the "recursion" case from the case where it terminates the recursion. (Otherwise there's no way to pick which overload to use). Actually you can drop M entirely though, but still need the enable_if – Flexo Aug 24 '12 at 12:11
  • @Flexo: Cant you use a template function specialization somehow? Just need to differentiate `gs<0,...>()` from `gs()` – Andrew Tomazos Aug 24 '12 at 12:19
  • @AndrewTomazos-Fathomling I think not - my compiler reports *"function template partial specialization ‘make<0, Vals ...>’ is not allowed"*, I'm not 100% sure if that's lack of implementation or correct behaviour though. I thought partial function template specializations were forbidden still. – Flexo Aug 24 '12 at 12:24
  • Hi, I'm wondering what kind of sorcery this is: `std::array{{f(S)...}};` does it have a name? – enobayram Aug 24 '12 at 13:49
  • @enobayram there's two new C++11 things in that: a) It's uniform initialisation (the { bits) b) it's unpacking a parameter pack of `int`s. Since the `...` is outside of `f` it expands to `f(0), f(1), f(2)` etc., if it were written `f(S...)` it would expand to `f(0,1,2` etc. – Flexo Aug 24 '12 at 13:56
  • For the `{{` see http://stackoverflow.com/a/11735045/168175 - it's not really needed for this case, but it's a habit I seem to have formed for `std::array`. – Flexo Aug 24 '12 at 14:06
  • 3
    Full braces (`{{ ... }}`) *are* needed in this case. – Luc Danton Aug 24 '12 at 14:40
4

Boost.Preprocessor can help you. The restriction, however, is that you have to use integral literal such as 10 instead of N (even be it compile-time constant):

#include <iostream>

#include <boost/preprocessor/repetition/enum.hpp>

#define VALUE(z, n, text) f(n)

//ideone doesn't support Boost for C++11, so it is C++03 example, 
//so can't use constexpr in the function below
int f(int x) { return x * 10; }

int main() {
  int const a[] = { BOOST_PP_ENUM(10, VALUE, ~) };  //N = 10
  std::size_t const n = sizeof(a)/sizeof(int);
  std::cout << "count = " << n << "\n";
  for(std::size_t i = 0 ; i != n ; ++i ) 
    std::cout << a[i] << "\n";
  return 0;
}

Output (ideone):

count = 10
0
10
20
30
40
50
60
70
80
90

The macro in the following line:

int const a[] = { BOOST_PP_ENUM(10, VALUE, ~) }; 

expands to this:

int const a[] = {f(0), f(1), ... f(9)}; 

A more detail explanation is here:

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 2
    A more useful test of correctness would be to decompile the .o file and verify that you see 10,20,30... in the data area. – Andrew Tomazos Aug 24 '12 at 11:28
4

If you want the array to live in static memory, you could try this:

template<class T> struct id { typedef T type; };
template<int...> struct int_pack {};
template<int N, int...Tail> struct make_int_range
    : make_int_range<N-1,N-1,Tail...> {};
template<int...Tail> struct make_int_range<0,Tail...>
    : id<int_pack<Tail...>> {};

#include <array>

constexpr int f(int n) { return n*(n+1)/2; }

template<class Indices = typename make_int_range<10>::type>
struct my_lookup_table;
template<int...Indices>
struct my_lookup_table<int_pack<Indices...>>
{
    static const int size = sizeof...(Indices);
    typedef std::array<int,size> array_type;
    static const array_type& get()
    {
        static const array_type arr = {{f(Indices)...}};
        return arr;
    }
};

#include <iostream>

int main()
{
    auto& lut = my_lookup_table<>::get();
    for (int i : lut)
        std::cout << i << std::endl;
}

If you want a local copy of the array to work on, simply remove the ampersand.

sellibitze
  • 27,611
  • 3
  • 75
  • 95
3

There are quite a few great answers here. The question and tags specify c++11, but as a few years have passed, some (like myself) stumbling upon this question may be open to using c++14. If so, it is possible to do this very cleanly and concisely using std::integer_sequence; moreover, it can be used to instantiate much longer arrays, since the current "Best I Have" is limited by recursion depth.

constexpr std::size_t f(std::size_t x) { return x*x; } // A constexpr function
constexpr std::size_t N = 5; // Length of array

using TSequence = std::make_index_sequence<N>;

static_assert(std::is_same<TSequence, std::integer_sequence<std::size_t, 0, 1, 2, 3, 4>>::value,
"Make index sequence uses std::size_t and produces a parameter pack from [0,N)");

using TArray = std::array<std::size_t,N>;

// When you call this function with a specific std::integer_sequence,
// the parameter pack i... is used to deduce the the template parameter
// pack.  Once this is known, this parameter pack is expanded in
// the body of the function, calling f(i) for each i in [0,N).

template<std::size_t...i>
constexpr TArray
get_array(std::integer_sequence<std::size_t,i...>)
{
  return TArray{{ f(i)... }}; 
}

int main()
{

  constexpr auto s = TSequence();
  constexpr auto a = get_array(s);

  for (const auto &i : a) std::cout << i << " ";  // 0 1 4 9 16

  return EXIT_SUCCESS;

}
sudo make install
  • 5,629
  • 3
  • 36
  • 48
2

I slightly extended the answer from Flexo and Andrew Tomazos so that the user can specify the computational range and the function to be evaluated.

#include <array>
#include <iostream>
#include <iomanip>

template<typename ComputePolicy, int min, int max, int ... expandedIndices> 
struct ComputeEngine
{
  static const int lengthOfArray = max - min + sizeof... (expandedIndices) + 1;
  typedef std::array<typename ComputePolicy::ValueType, lengthOfArray> FactorArray;

  static constexpr FactorArray compute( )
  {
    return ComputeEngine<ComputePolicy, min, max - 1, max, expandedIndices...>::compute( );
  }
};

template<typename ComputePolicy, int min, int ... expandedIndices> 
struct ComputeEngine<ComputePolicy, min, min, expandedIndices...>
{
  static const int lengthOfArray = sizeof... (expandedIndices) + 1;
  typedef std::array<typename ComputePolicy::ValueType, lengthOfArray> FactorArray;

  static constexpr FactorArray compute( )
  {
    return FactorArray { { ComputePolicy::compute( min ), ComputePolicy::compute( expandedIndices )... } };
  }
};

/// compute 1/j
struct ComputePolicy1
{
  typedef double ValueType;

  static constexpr ValueType compute( int i )
  {
    return i > 0 ? 1.0 / i : 0.0;
  }
};

/// compute j^2
struct ComputePolicy2
{
  typedef int ValueType;

  static constexpr ValueType compute( int i )
  {
    return i * i;
  }
};

constexpr auto factors1 = ComputeEngine<ComputePolicy1, 4, 7>::compute( );
constexpr auto factors2 = ComputeEngine<ComputePolicy2, 3, 9>::compute( );

int main( void )
{
  using namespace std;

  cout << "Values of factors1" << endl;
  for ( int i = 0; i < factors1.size( ); ++i )
  {
    cout << setw( 4 ) << i << setw( 15 ) << factors1[i] << endl;
  }
  cout << "------------------------------------------" << endl;

  cout << "Values of factors2" << endl;
  for ( int i = 0; i < factors2.size( ); ++i )
  {
    cout << setw( 4 ) << i << setw( 15 ) << factors2[i] << endl;
  }

  return 0;
}
NilZ
  • 51
  • 5
1

Here's a more concise answer where you explicitly declare the elements in the original sequence.

#include <array>

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

template <int... Ts>
struct sequence
{
    using result = sequence<f(Ts)...>;
    static std::array<int, sizeof...(Ts)> apply() { return {{Ts...}}; }
};

using v1 = sequence<1, 2, 3, 4>;
using v2 = typename v1::result;

int main()
{
    auto x = v2::apply();
    return 0;
}
void-pointer
  • 14,247
  • 11
  • 43
  • 61
  • 1
    If you're going to do it that way then you can just declare one template function: `template array fs() { return {{f(i)...}}; }` but entering all the indexes is tedious for large N. – Andrew Tomazos Aug 24 '12 at 20:48
  • True; the reason I didn't do that was to show how you can declare the elements independently of the function call. – void-pointer Aug 25 '12 at 00:03
1

How about this one?

#include <array>
#include <iostream>

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

template <int N, int... Ts>
struct t { using type = typename t<N - 1, Ts..., 101 - N>::type; };

template <int... Ts>
struct t<0u, Ts...>
{
    using type = t<0u, Ts...>;
    static std::array<int, sizeof...(Ts)> apply() { return {{f(Ts)...}}; }
};

int main()
{
    using v = typename t<100>::type;
    auto x = v::apply();
}
void-pointer
  • 14,247
  • 11
  • 43
  • 61
0

I don't think that's the best way to do this, but one can try somewhat like this:

#include <array>
#include <iostream>
#include <numbers>

constexpr auto pi{std::numbers::pi_v<long double>};

template <typename T>
struct fun
{
    T v;
    explicit constexpr fun(T a) : v{a * a} {}
};

template <size_t N, typename T, typename F>
struct pcl_arr
{
    std::array<T, N> d;

    explicit constexpr pcl_arr()
    : d{}
    {
        for (size_t i{}; i < N; d[i] = !i ? 0. : F(pi + i).v, ++i);
    }
};

int main()
{
    using yummy = pcl_arr<10, long double, fun<long double>>;

    constexpr yummy pies;
    
    std::array cloned_pies{pies.d};

    //  long double comparison is unsafe
    //  it's just for the sake of example
    static_assert(pies.d[0] == 0.);

    for (const auto & pie : pies.d)         { std::cout << pie << ' '; } std::cout << '\n';
    for (const auto & pie : cloned_pies)    { std::cout << pie << ' '; } std::cout << '\n';

    return 0;
}

godbolt.org x86-x64 gcc 11.2 -Wall -O3 -std=c++20 output:

0 17.1528 26.436 37.7192 51.0023 66.2855 83.5687 102.852 124.135 147.418

0 17.1528 26.436 37.7192 51.0023 66.2855 83.5687 102.852 124.135 147.418

Xofrio
  • 15
  • 2