Programmatically create static arrays at compile t

2019-01-02 14:54发布

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.

10条回答
步步皆殇っ
2楼-- · 2019-01-02 15:04

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.

查看更多
看淡一切
3楼-- · 2019-01-02 15:05

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

查看更多
初与友歌
4楼-- · 2019-01-02 15:07

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.

查看更多
心情的温度
5楼-- · 2019-01-02 15:11

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.

查看更多
栀子花@的思念
6楼-- · 2019-01-02 15:12

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.

查看更多
余生无你
7楼-- · 2019-01-02 15:23

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";
}
查看更多
登录 后发表回答