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;
}
15
  • 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. Commented Aug 24, 2012 at 11:20
  • 2
    @AndrewTomazos-Fathomling You will notice that I said if this never changes. Who maintains a list of Fibonacci numbers? Commented Aug 24, 2012 at 11:25
  • 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. Commented Aug 24, 2012 at 11:31
  • 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) Commented Aug 24, 2012 at 13:46
  • 2
    @enobayram: Yes, the data is in the .rodata section as expected. Commented Aug 24, 2012 at 14:02

8 Answers 8

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.

Sign up to request clarification or add additional context in comments.

13 Comments

This is pretty good, but I think it can be done with a few less moving parts.
@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.
Is std::array<int,N> 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 - 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.
Full braces ({{ ... }}) are needed in this case.
|
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:

1 Comment

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.
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.

Comments

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;

}

Comments

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;
}

Comments

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;
}

2 Comments

If you're going to do it that way then you can just declare one template function: template<int...i> array<int,N> fs() { return {{f(i)...}}; } but entering all the indexes is tedious for large N.
True; the reason I didn't do that was to show how you can declare the elements independently of the function call.
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();
}

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.