27

There was an answer on stackoverflow (which I can't seem to find anymore) which demonstrated how a variadic template can be used in C++11 to create a static array at compile time:

template <class T, T... args> 
struct array_
{
    static const T data[sizeof...(args)];
};

template <class T, T... args> 
const T array_<T, args...>::data[sizeof...(args)] = { args... };

A recursive meta-function could be provided to instantiate array_ with any number of parameters, which will then be copied at compile time into the internal array. It's a useful way to create meta-functions for generating constant arrays at compile time.

However, one problem is that it depends on class template parameters to get the actual values to populate the array. This results in one major limitation: only integral constants can be used as value template parameters. So, you can't use this technique to generate arrays of custom types.

I tried to think of something to work around this limitation, but can't come up with anything. Is there any way to make this technique work with non-integral constants?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Channel72
  • 24,139
  • 32
  • 108
  • 180
  • A std::string is likely to have a dynamic memory allocation internally. How would you do that at compile time? :-) – Bo Persson May 19 '11 at 15:00
  • 1
    @Bo, yes an std::string was a bad example. But what about a custom POD struct? – Channel72 May 19 '11 at 15:01
  • I believe this is the [link](http://stackoverflow.com/a/2981617/1053968) referred to in the question. – Tanner Sansbury Jun 03 '12 at 21:18
  • Look at my question, there is a good answer: http://stackoverflow.com/a/20388055/293195 – Gabriel Dec 05 '13 at 09:56
  • @BoPersson it's impossible to create compile-time string, but it's possible to create compile-time c-string and convert it to string in runtime (or, if using c++17, to string_view in compile-time too). [Here](https://pastebin.com/fQ7GBxCi) you can find an example useage of this technique along with some related meta-functions. – Denis Sheremet Dec 01 '17 at 05:45

3 Answers3

6

Well, you can indeed populate a static array with custom types (i.e. classes) instances provided that they are constructable from integer types (or whatever other types one may provide as non template parameters and which I am not going enumerate here).

Just take look at the example bellow, which I believe is clear enough to be self explaining:

#include <iostream>

template<typename T>
class my_class
{
    public:
        my_class(T)
        {
            //construct
        }

        void print_something()
        {
            std::cout << "something\n";
        }
};

template<class C, class T, T ... args>
struct array_
{
        static C data[sizeof...(args)];
};

template<class C, class T, T ... args>
C array_<C, T, args...>::data[sizeof...(args)] = {C(args)...};

int main()
{
    array_<my_class<int> , int, 1, 200, 0, 42>::data[2].print_something();
}

Note: compiled just fine under GCC 4.6

brunocodutra
  • 2,329
  • 17
  • 23
2

In C++11 (and especially in C++14), the best way to initialize objects at compile-time is with constexpr constructors, not by playing metagames with the typesystem.

struct MyObject {
    int x_, y_;
    constexpr MyObject(int x, int y) : x_(x), y_(y) { }
};

const MyObject array[] = { MyObject(1,2), MyObject(3,4) };

You can apply your "generator function" idea here, too, if you really want to:

#include <stdio.h>

#if __cplusplus < 201400
template<size_t... II> struct integer_sequence { typedef integer_sequence type; };
template<size_t N, size_t... II> struct make_index_sequence;
template<size_t... II> struct make_index_sequence<0, II...> : integer_sequence<II...> {};
template<size_t N, size_t... II> struct make_index_sequence : make_index_sequence<N-1, N-1, II...> {};
#define HACK(x) typename x::type
#else
#include <utility>  // the C++14 way of doing things
using std::integer_sequence;
using std::make_index_sequence;
#define HACK(x) x
#endif


struct MyObject {
    int x_, y_;
    constexpr MyObject(int x, int y) : x_(x), y_(y) { }
};

template<typename T, int N, T (*Func)(int), typename Indices>
struct GeneratedArrayHelper;

template<typename T, int N, T (*Func)(int), size_t... i>
struct GeneratedArrayHelper<T, N, Func, integer_sequence<i...>> {
    static const T data[N];  // element i is initialized with Func(i)
};

template<typename T, int N, T (*Func)(int), size_t... i>
const T GeneratedArrayHelper<T,N,Func, integer_sequence<i...>>::data[N] =
    { Func(i)... };

template<typename T, int N, T (*Func)(int)>
struct GeneratedArray :
    GeneratedArrayHelper<T, N, Func, HACK(make_index_sequence<N>)> {};

constexpr MyObject newObject(int i) { return MyObject(2*i, 2*i+1); }

int main() {
    for (const MyObject& m : GeneratedArray<MyObject, 5, newObject>::data) {
        printf("%d %d\n", m.x_, m.y_);
    }

    // Output:
    //   0 1
    //   2 3
    //   4 5
    //   6 7
    //   8 9
}

I don't know why Clang 3.5 and GCC 4.8 insist that I put the HACK() macro in there, but they refuse to compile the code without it. Probably I made some dumb mistake and someone can point it out. Also, I'm not confident that all the consts and constexprs are in the best places.

Quuxplusone
  • 23,928
  • 8
  • 94
  • 159
  • There is one good reason to play metaprogramming games: if you want to ensure you only have a single instance for each expression. You could use a hash table or something, but that is unneeded if you do the template foo. – Christopher Smith Aug 23 '14 at 21:49
1

Non-type template arguments can also be pointers or references, provided they point or refer to an object with external linkage.

template<typename T, T& t>
struct ref {
    static T&
    get() { return t; }
};

int i = 0;
int& ri = ref<int, i>::get(); // ok

static int j = 0;
int& rj = ref<int, j>::get(); // not ok

const int jj = 0; // here, const implies internal linkage; whoops
const int& rjj = ref<const int, jj>::get(); // not ok

extern const int k = 0;
const int& rk = ref<const int, k>::get(); // ok

namespace {
int l = 0;
}
int& rl = ref<int, l>::get(); // ok, and l is specific to the TU

I don't think you'd really want to init the elements with extern references though, since that would end up with twice the number of objects. You could initialize the elements of the array from literals, but unfortunately you can't use string literals as template arguments. So you'd need the proverbial layer of indirection: It's painful because arrays or array references can't appear in a template parameter list (I guess this is why string literals can't):

// Not possible:
// const char* lits[] = { "Hello, ", "World!" };
// lit accepts const char*&, not const char*
// typedef array_<T, lit<lits[0]>, lit<lits[1]>, int_<42> > array;

// instead, but painful:
const char* hello = "Hello";
const char* world = "World!";
typedef array_<T, lit<hello>, lit<world>, int_<42> > array;
/*
 * here array::data would be an array of T, size 3,
 * initialized from { hello, world, 42 }
 */

I can't see how to avoid dynamic initialization without C++0x's constexpr, and even then there are limitations. Using some kind of tuple to build composite initializers (e.g. initialize from { { hello, world, 42 }, ... }) left as an exercise. But here's an example.

Community
  • 1
  • 1
Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • 1
    `std::string` can't be constructed `constexpr`, since it uses dynamic allocation. – Ben Voigt May 19 '11 at 17:07
  • @Ben Actually since I'm using literals the problem arises when initializing from a composite of literals (please don't quote me out of context). If the element type `T` cannot be statically initialized, then obviously that can't happen, but that's unrelated (to both the question and my answer). – Luc Danton May 19 '11 at 17:08
  • For posterity, `template` arguments are no longer required to have external linkage, so things in an unnamed namespace for example are OK. – underscore_d Jul 18 '16 at 09:05