75

One can define a static array at compile time as follows:

const std::size_t size = 5;    
unsigned int list[size] = { 1, 2, 3, 4, 5 };

Question 1 - Is it possible by using various kinds of metaprogramming techniques to assign these values "programmatically" at compile time?

Question 2 - Assuming all the values in the array are to be the same barr a few, is it possible to selectively assign values at compile time in a programmatic manner?

eg:

const std::size_t size = 7;        
unsigned int list[size] = { 0, 0, 2, 3, 0, 0, 0 };
  1. Solutions using C++0x are welcome
  2. The array may be quite large, few hundred elements long
  3. The array for now will only consist of POD types
  4. It can also be assumed the size of the array will be known beforehand, in a static compile-time compliant manner.
  5. Solutions must be in C++ (no script, no macros, no pp or code generator based solutions pls)

UPDATE: Georg Fritzsche's solution is amazing, needs a little work to get it compiling on msvc and intel compilers, but nonetheless a very interesting approach to the problem.

Hippicoder
  • 1,565
  • 3
  • 17
  • 21
  • 6
    @GMan: The picture is as I've explained it, want to know if its possible to populate a static array at compile time using only c++. no hidden agendas etc. – Hippicoder Jun 04 '10 at 22:54
  • 3
    @Hippicoder @GMan's comment is relevant, because you can't do it in C++ nor in C++0x. Provide readers with the context, and the gurus would find you a (alternative)suitable solution for the original problem. – Khaled Alshaya Jun 04 '10 at 22:54
  • @Hippicoder: Well, for example, where do the values in your example come from? What is their relation to the compile-time environment? Are they from a text file? Are they flags saying whether macros are defined? Are they counters of some kind? – James McNellis Jun 04 '10 at 22:57
  • Also, do you need to index the values at compile-time or at run-time? – Khaled Alshaya Jun 04 '10 at 22:58
  • 5
    Assume a process requires a LUT, depending on the mode of the process the LUTs are the same except for some of the values, all the other values are the same or can be generated by evaluting a simple sequence like f(n) = 2*n or f(n) = 1 + n etc... – Hippicoder Jun 04 '10 at 23:03
  • @Hippy, where I work code is being generated all of the time and we have not gone out of business yet. – Hamish Grubijan Jun 04 '10 at 23:11
  • 2
    I think the first could be done with a recursive template and passing a constant + 1 to each deeper level. I'm looking into that now. – Michael Dorgan Jun 04 '10 at 23:15
  • 5
    @Michael Dorgan: I thought about that too, but cant seem to come up with the right way to do it, atm my solution involves getting a value from an enum off-of a templated struct, but still requires me to instantiate n templates which increases the compile time greatly. – Hippicoder Jun 04 '10 at 23:19
  • 2
    Template meta-programming only gives you recursive structures, the best you could get is an instantation depth of `N/M` instead of `N`. – Georg Fritzsche Jun 05 '10 at 16:33

14 Answers14

88

The closest you can get is using C++0x features to initialize local or member arrays of templates from a variadic template argument list.
This is of course limited by the maximum template instantiation depth and wether that actually makes a notable difference in your case would have to be measured.

Example:

template<unsigned... args> struct ArrayHolder {
    static const unsigned data[sizeof...(args)];
};

template<unsigned... args> 
const unsigned ArrayHolder<args...>::data[sizeof...(args)] = { args... };

template<size_t N, template<size_t> class F, unsigned... args> 
struct generate_array_impl {
    typedef typename generate_array_impl<N-1, F, F<N>::value, args...>::result result;
};

template<template<size_t> class F, unsigned... args> 
struct generate_array_impl<0, F, args...> {
    typedef ArrayHolder<F<0>::value, args...> result;
};

template<size_t N, template<size_t> class F> 
struct generate_array {
    typedef typename generate_array_impl<N-1, F>::result result;
};

Usage for your 1..5 case:

template<size_t index> struct MetaFunc { 
    enum { value = index + 1 }; 
};

void test() {
    const size_t count = 5;
    typedef generate_array<count, MetaFunc>::result A;

    for (size_t i=0; i<count; ++i) 
        std::cout << A::data[i] << "\n";
}
Georg Fritzsche
  • 97,545
  • 26
  • 194
  • 236
  • 2
    A note regarding template instantiation depth, msvc dies at around 1000, gcc has an option to set the recursive depth, I've been able to create a 512 element lut with this suggestion - compile times are obviously a little longer than having the lut hard-coded in source, but all-in-all it works fine!!! :D – Hippicoder Jun 06 '10 at 08:10
  • 1
    Amazing! It essentially allows the concatenation / extension of arrays I could not realize in C++03 with metatemplate. I think you should parameterize `ArrayHolder` with the MetaFunction though, in order to be able to define more than 1 array with a given arity. – Matthieu M. Jun 06 '10 at 10:40
  • @Matthieu: If the values differ its a different type, if they are the same i don't want a duplicate type here - am i missing something? – Georg Fritzsche Jun 06 '10 at 12:07
  • 2
    as a work around for the fairly limited recursion depth some compilers allow, one may add more then one value to the "variadic values list" each step, decreasing the required depth M times, where M is the number of values added. For instance, for M=2 we have: template class F, unsigned... args> struct generate_array_impl { typedef typename generate_array_impl::value, F::value, args...>::result result; }; But please do not forget to treat the case where N%M != 0 – brunocodutra Jun 20 '11 at 15:43
  • 1
    +100 I was about to throw `std::initializer_list` for constructors out the window, until I found your answer. It'll certainly be a while until I understand *how this works*, but I am in awe of this amazing bridge from compile-time to run-time. TYVM. – kfmfe04 Feb 16 '13 at 18:41
  • What does the `...` stand for? – ButterDog Jun 25 '14 at 07:30
  • @GeorgFritzsche Can this be made to work to hold non-integral types like float or double? – Luke Peterson Jul 24 '15 at 00:03
  • 1
    @Xocoatzin That is the parameter pack expansion, see e.g. [here](http://en.cppreference.com/w/cpp/language/parameter_pack) – Georg Fritzsche Jul 24 '15 at 12:04
  • @hazelnusse Not directly, but can't you use `constexpr` functions? Alternatives are a bit crude, like basically [reimplementing floating point semantics](http://stackoverflow.com/questions/25272844/alternatives-for-compile-time-floating-point-initialization). – Georg Fritzsche Jul 24 '15 at 12:14
  • Why do you need `generate_array` for? It works pretty much the same if you remove it and use `generate_array_impl` directly in your `test` function: `typedef generate_array_impl::result A;`. – Mikhail Dec 09 '15 at 16:52
  • 1
    Are there better ways of doing this in 2017? This solution looks terrible. Why can't we just have delayed initialization inside constexpr functions? – Willy Goat Mar 31 '17 at 06:59
13

Since C++17 you can use a constexpr lambda an invoke it in place. The only "downside" is that you will have to use std::array instead of c-style array:

constexpr auto myArray{[]() constexpr{
    std::array<MyType, MySize> result{};
    for (int i = 0; i < MySize; ++i)
    {
       result[i] = ...
    }
    return result;
}()};

As an example that's how you could create an array with powers of two:

constexpr auto myArray{[]() constexpr{
    constexpr size_t size = 64;
    std::array<long long, size> result{};
    result[0] = 1;
    for (int i = 1; i < size; ++i)
    {
       result[i] = result[i - 1] * 2;
    }
    return result;
}()};

As you can see, you can even reference the previous cells of the array.

This technique is called IILE or Immediately Invoked Lambda Expression.

janekb04
  • 4,304
  • 2
  • 20
  • 51
  • This will not compile because of `error: overflow in constant expression`. Using some other initialization like `result[i] = i` or decreasing the size to 32 or using `unsigned long long` instead of `long long` will make it compile. Love the implicit undefined behavior checks in `constexpr` annotated functions. But as this is not `consteval` (C++20), there is no guarantee that this IILE is executed at compile-time afaik. – mxmlnkn Feb 07 '22 at 21:33
  • Testing it on godbolt, it seems to work well enough though: https://godbolt.org/z/1n6h3Evvs – mxmlnkn Feb 07 '22 at 21:40
  • @mxmlnkn There is a guarantee. The lambda doesn't have to be `consteval` as `myArray` is `constexpr`, so it has to be initialised at compile time. As long as `constexpr auto myArray{whatever};` compiles, `whatever` had to be known at compile time. Correct me, if I'm wrong. – janekb04 Feb 07 '22 at 23:14
6

Well your requirements are so vague it's difficult to do anything about them... The main issue is of course: where do those value come from ?

Anyway a build in C++ can be thought of as 4 steps:

  • Pre-build steps: script generation of header/source from other formats
  • Preprocessing
  • Template instantiations
  • Compilation proper

If you wish to rule out the script generation, then you're left with 2 alternatives: Preprocessing and Meta-template programming.

There is just no way I know of for meta-template programming to do the trick here, because as far as I know it's not possible to concatenate two arrays at compile time. Thus we are left with the savior of the day: Preprocessor Programming

I would suggest using a full-fledged library to help us out: Boost.Preprocessor.

Of particular interest here:

Now if only we knew where to pick the values from, we could give more meaningful examples.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 3
    Check out Georg Fritzsche's answer: using C++0x variadic templates and initialization of static arrays from varadic list he was able to come up with a metatemplate solution! – Matthieu M. Jun 06 '10 at 10:41
4

How about building a nested struct using templates, and casting that as an array of the right type. The example below works for me, but I have a feeling I'm either treading in or walking very close to undefined behaviour.

#include <iostream>

template<int N>
struct NestedStruct
{
  NestedStruct<N-1> contained;
  int i;
  NestedStruct<N>() : i(N) {}
};

template<>
struct NestedStruct<0> 
{
  int i;
  NestedStruct<0>() : i(0) {}
};

int main()
{
  NestedStruct<10> f;
  int *array = reinterpret_cast<int*>(&f);
  for(unsigned int i=0;i<10;++i)
  {
    std::cout<<array[i]<<std::endl;
  }
}

And of course you could argue that the array is not initialised at compile time (which I think is impossible) but the values that will go into the array are calculated at compile time, and you can access them as you would a normal array... I think that's as close as you can get.

Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
  • 1
    That `reinterpret_cast` sets undefined behaviour alarm bells ringing in my head. – Flexo Aug 21 '11 at 11:27
  • 2
    We can avoid the `reinterpret_cast` by using `&f.i-10` or adding a recursive `int* start()` function instead. However the question really is "does the compiler insert padding between `contained` and `i` in the nested struct?". I see no reason why it would, as `NestedStruct` and `int` will have the same alignment requirements. However I don't think there's anything in the spec which would prohibit insertion of padding in this case. (Maybe a better language lawyer than me would know for sure though). – Michael Anderson Aug 22 '11 at 02:48
3

Just use a code generator. Build one or more templates that can generate the code you want, using a table or even math functions. Then include the file you generated in your app.

Seriously, a code generator would make your life much easier.

Rui Curado
  • 951
  • 5
  • 12
  • Two people have flagged this as spam. It doesn't appear to be spam to me, *except* your code generator isn't yet available, so mentioning it doesn't help to answer the question. (Editing the answer once your tool is available would be different.) – And I'm also a big fan of code generation, it really will make his life easier. ;) –  Jun 06 '10 at 09:38
  • @Roger: I've edited my answer and removed all references to the product. – Rui Curado Jun 07 '10 at 10:17
  • Now it's definitely worth an upvote! Self-promotion is a tricky business on SO. –  Jun 07 '10 at 20:56
  • The code generator can be `array_type user_impl(size_t index);` Use `std::cout` and a comma to generate the array body. You can use `#include` to include the generated body in the code. Just code it like run-time initialization and then use a host built binary to generate the array. For most users, host, build, and target are all the same. – artless noise Dec 30 '20 at 13:40
3

Sometime (not always) such array is generated from array of types. For example if you already have variadic class list (like template) and want to store encapsulated uint32_t value you can use:

uint32_t tab[sizeof(A)]= {A::value...};
kwesolowski
  • 695
  • 8
  • 18
2

Do you really need to do it at compiler time? It would be much easier to do at static initialization time. You could do something like this.

#include <cstddef>
#include <algorithm>

template<std::size_t n>
struct Sequence
{
    int list[n];

    Sequence()
    {
        for (std::size_t m = 0; m != n; ++m)
        {
            list[m] = m + 1;
        }
    }
};

const Sequence<5> seq1;

struct MostlyZero
{
    int list[5];

    MostlyZero()
    {
        std::fill_n(list, 5, 0); // Not actually necessary if our only
                                 // are static as static objects are
                                 // always zero-initialized before any
                                 // other initialization
        list[2] = 2;
        list[3] = 3;
    }
};

const MostlyZero mz1;

#include <iostream>
#include <ostream>

int main()
{
    for (std::size_t n = 0; n != 5; ++n)
    {
        std::cout << seq1.list[n] << ", " << mz1.list[n] << '\n';
    }
}

You could push the lists outside of the structs if you wanted but I thought it was a bit cleaner like this.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • 7
    The values are not present at compile-time - I think if what i wanted was as simple as that I could just as easily written a function to populate a std::vector... thanks for the attempt though. – Hippicoder Jun 04 '10 at 23:13
  • 7
    @Hippicoder: If the values aren't present at compile-time then how are you going to programmatically assign them at compile-time as your question asks? – CB Bailey Jun 04 '10 at 23:18
  • 4
    I believe he's trying to say your code isn't generating them at compile time. Your code is creating the array at runtime, and thus doesn't fit his overly tight requirements... – Thanatos Jun 05 '10 at 06:49
2

Something like Boost.Assignment could work for standard containers. If you really need to use arrays, you can use it along Boost.Array.

danielkza
  • 2,517
  • 1
  • 22
  • 20
1

the 1't question. You can do it like that.

template <int num, int cur>
struct ConsequentListInternal {
    enum {value = cur};
    ConsequentListInternal<num-1,cur+1> next_elem;
};

template <int cur>
struct ConsequentListInternal<0, cur> {
    enum {value = cur};
};

template <int v>
struct ConsequentList {
    ConsequentListInternal<v, 0> list;
};

int main() {
    ConsequentList<15> list;
    return 0;
}
Max
  • 4,792
  • 4
  • 29
  • 32
  • 5
    Ok.... how would I get the ith value from the list, with a run-time generated "i" ? ps: please read the comment to Michael Dorgan's solution. – Hippicoder Jun 04 '10 at 23:43
1

array<int, SIZE> t

As mentionned, with C++17 you can use constexpr

vector<int> countBits(int num) {
    static constexpr int SIZE = 100000;
    static constexpr array<int, SIZE> t {[]() constexpr {
            constexpr uint32_t size = SIZE;
            array<int, size> v{};
            for (int i = 0; i < size; i++)
                v[i] =  v[i>>1] + (i & 1); // or simply v[i] = __builtin_popcount(i);
            return v;}()};

    vector<int> v(t.begin(), t.begin() + num + 1);
    return v;
}

However you will have to use the c++ array type.


int t[SIZE]

If you really want to use a C array int [SIZE], different from array<int, SIZE> use the following trick:

Declare a global array and then compute values inside the main to create the static array at compile time:

int w[100000] = {0};

vector<int> countBits(int num) {
    vector<int> v(w, w + num + 1);
    return v;
}

int main(void) {
    for (int i = 0; i < 100000; i++)
        w[i] = __builtin_popcount(i);
}


Results

Output at runtime (awful indeed):

OK  ( 591 cycles)        0,1,1, -> 0,1,1,
OK  ( 453 cycles)        0,1,1,2,1,2, -> 0,1,1,2,1,2,
OK  ( 455 cycles)        0,1,1,2,1,2,2,3,1,2,... -> 0,1,1,2,1,2,2,3,1,2,...

Average Output with the constexpr array:

OK  (   1 cycles)        0,1,1, -> 0,1,1,
OK  (   2 cycles)        0,1,1,2,1,2, -> 0,1,1,2,1,2,
OK  (  24 cycles)        0,1,1,2,1,2,2,3,1,2,... -> 0,1,1,2,1,2,2,3,1,2,...

Average Output with the second method (slightly faster as we get rid of the overhead of C++ array):

OK  (   0 cycles)        0,1,1, -> 0,1,1,
OK  (   1 cycles)        0,1,1,2,1,2, -> 0,1,1,2,1,2,
OK  (  23 cycles)        0,1,1,2,1,2,2,3,1,2,... -> 0,1,1,2,1,2,2,3,1,2,...

Benchmark

I benchmarked with:

#include <vector>
#include <string>
#include <cstdint>
#include <array>
#include <iostream>
#include <ctime>
#include <iterator>
#include <sstream>

using namespace std;

vector<int> nums = {2, 5};
vector<vector<int>> expected = {{0,1,1}, {0,1,1,2,1,2}}; // feel free to add more tests

for (int i = 0; i < expected.size(); i++) {
        clock_t start = clock();
        vector<int> res = countBits(nums[i]);
        double elapsedTime = (clock() - start);
        printf("%s  \033[30m(%4.0lf cycles)\033[0m\t %s -> %s\n", (expected[i] == res) ? "\033[34mOK" : "\033[31mKO", elapsedTime, toString(res).c_str(), toString(expected[i]).c_str());
}
Antonin GAVREL
  • 9,682
  • 8
  • 54
  • 81
1

Over time, the capabilities of constexpr functions, methods and lambdas have greatly improved in C++. With C++17, you may use for loops and if conditions to actually calculate the contents of an constexpr array at compile time. See this example for a prime number sieve:

#include <array>
#include <cmath>

template<unsigned N>
constexpr auto primesieve() {
    std::array<bool, N+1> primes {};
    // From C++20, the init loop may be written as:   primes.fill(true);
    for(unsigned n = 0; n <= N; n++) {
        primes[n] = true;
    }
    unsigned maxs = sqrt(N);
    for(unsigned n = 2; n <= maxs; n++) {
        if(primes[n]) {
            for(unsigned j = n + n; j <= N; j += n) {
                primes[j] = false;
            }
        }
    }
    return primes;
};

extern constexpr std::array<bool, 20> myprimes { primesieve<19>() };

When you look at the assembly output of this code, you will see only the data bytes of the myprimes array, but not a single processor instruction. All calculations are performed at compile time, even if optimization is turned off.

However, as others have already written: Interpreting C++ code in the compiler is much slower than running compiled C++ code. So those initializations, that can reasonably be done at compile time, would take at most a few milliseconds at run time.

But const / constexpr initialization has many advantages. Namely they go to constant memory, that is shared between different processes running the same application. On the other hand, dynamic initialization at run time goes to private memory of each process.

And the capabilities are further improving. C++20 even adds support for std::string and std::vector in constexpr functions. However, you are not able to return non-empty strings and vectors from constexpr functions, and until now, only the Microsoft compiler has implemented this feature.

Kai Petzke
  • 2,150
  • 21
  • 29
0

There's a lot of things you can do with meta-programming. But first I'd like to ask: why would you want to do this in your case? I could understand if you needed to declare such an array in different places, so that it'd demand rewriting the same things multiple times. Is this your case?

By saying "define programmatically" I suggest the following:

#define MyArr(macro, sep) \
    macro(0) sep \
    macro(0) sep \
    macro(2) sep \
    macro(3) sep \
    macro(0) sep \
    macro(0) sep \
    macro(0)

By now we've defined all the values you wanted in the most abstract way. BTW if those values actually mean something for you - you could add it to the declaration:

#define MyArr(macro, sep) \
    macro(0, Something1) sep \
    macro(0, Something2) sep \
    // ...

Now let's breath life into the above declaration.

#define NOP
#define COMMA ,
#define Macro_Count(num, descr) 1
#define Macro_Value(num, descr) num

const std::size_t size = MyArr(Macro_Count, +); 
unsigned int list[size] = { MyArr(Macro_Value, COMMA) };

You can also handle the situation where most of your array entries are the same, with some perverted creativity :)

But you should always ask yourself: is this really worth it? Because, as you can see, you turn the code into a puzzle.

valdo
  • 12,632
  • 2
  • 37
  • 67
  • Why would you push something back to runtime that should be calculable at compile time? He has to turn the code into a puzzle because of gaps in the C++ language. – jww Dec 07 '16 at 08:56
0

from boost,

boost::mpl::range_c<int,1,5>

Will generate a list of sorted numbers from 1 to 5 at compile time. For the second, you mention no criteria for which values would be changed. I'm pretty sure you can't undef then redef a new var once a list is created.

Evan Teran
  • 87,561
  • 32
  • 179
  • 238
Michael Dorgan
  • 12,453
  • 3
  • 31
  • 61
  • 3
    the with range_c and other mpl style arrays is that, they don't have a random access operator, or if they do it requires a compile-time index value. I'd like to be able to use the array as i would a static array at run-time with run-time generated index values. – Hippicoder Jun 04 '10 at 23:35
0

use template recursive

template<uint64_t N>
constexpr uint64_t Value()
{
    return N + 100;
}

// recursive case
template<uint64_t N, uint64_t... args>
struct Array : Array<N - 1, Value<N - 1>(), args...> {
};

// base case
template<uint64_t... args>
struct Array<0, Value<0>(), args...> {
    static std::array<uint64_t, sizeof...(args) + 1> data;
};

template<uint64_t... args>
std::array<uint64_t, sizeof...(args) + 1> Array<0, Value<0>(), args...>::data = {Value<0>(), args...};

int main()
{
    Array<10> myArray;
    for (size_t i = 0; i < myArray.data.size(); ++i) {
        cout << myArray.data[i] << endl;
    }

    return 0;
}