21

I'm looking to create a lookup table of coordinates, something like:

int a[n][2] = {{0,1},{2,3}, ... } 

For a given n, to be created at compile time. I started looking into constexpr, but is seems like a function returning a constexpr std::vector<std::array <int, 2> > isn't an option, as I get:

invalid return type 'std::vector<std::array<int, 2ul> >' of constexpr function 

How can create such a compile time array?

5
  • 1
    std::vector is not a literal type and therefore cannot be used in C++11 constexpr. C++11's array type lacks constexpr accessors and therefore also has limited use in constexpr functions. If you don't have some of the C++1y lib/compiler support, I suggest using a custom array type instead. Commented Sep 25, 2013 at 22:23
  • @DyP - could you show an example? Commented Sep 25, 2013 at 22:25
  • Would be more useful if you added some details of what you want to do ;) Commented Sep 25, 2013 at 22:25
  • @DyP - Just create a list of coordinates that I'd like to have at compile time. An example would be ~100 points on a line. Commented Sep 25, 2013 at 22:28
  • 1
    N.B. in C++1y, you can also use an initializer_list, as they're required to be literal types in C++1y. Commented Sep 25, 2013 at 22:33

3 Answers 3

28

With C++14, you do not need too much template magic. Here an example of how to have a lookup table for f(x) = x^3 with the first coordinate being the x value and the second coordinate being the f(x) value:

#include <iostream> template<int N> struct Table { constexpr Table() : values() { for (auto i = 0; i < N; ++i) { values[i][0] = i; values[i][1] = i * i * i; } } int values[N][2]; }; int main() { constexpr auto a = Table<1000>(); for (auto x : a.values) std::cout << "f(" << x[0] << ") = " << x[1] << '\n'; } 
Sign up to request clarification or add additional context in comments.

6 Comments

What is the meaning of: : values() in constexpr Table() : values()? Does it zero-initialize or something?
&Jonas Yes, it zero-initializes which is necessary because C++ needs to have a compile-const value. It will be set in the constructor later but this is hard to track by the compiler
@IceFire are you sure? according to en.cppreference.com/w/cpp/container/array/operator_at non-const operator[] is constexpr only since C++17 version. This therefore can be troublesome
@DawidPi We do not have an std::array here, though
It may be noteworthy that this approach may cause issues with the compiler running out of heap for sufficiently large N which is not that unlikely for large lookup tables.
|
20

I'll dump the code first, adding references and comments where necessary/appropriate later. Just leave a comment if the result is somewhat close to what you're looking for.

Indices trick for pack expansion (required here to apply the generator), by Xeo, from this answer, modified to use std::size_t instead of unsigned.

#include <cstddef> // by Xeo, from https://stackoverflow.com/a/13294458/420683 template<std::size_t... Is> struct seq{}; template<std::size_t N, std::size_t... Is> struct gen_seq : gen_seq<N-1, N-1, Is...>{}; template<std::size_t... Is> struct gen_seq<0, Is...> : seq<Is...>{}; 

Generator function:

#include <array> template<class Generator, std::size_t... Is> constexpr auto generate_array_helper(Generator g, seq<Is...>) -> std::array<decltype(g(std::size_t{}, sizeof...(Is))), sizeof...(Is)> { return {{g(Is, sizeof...(Is))...}}; } template<std::size_t tcount, class Generator> constexpr auto generate_array(Generator g) -> decltype( generate_array_helper(g, gen_seq<tcount>{}) ) { return generate_array_helper(g, gen_seq<tcount>{}); } 

Usage example:

// some literal type struct point { float x; float y; }; // output support for `std::ostream` #include <iostream> std::ostream& operator<<(std::ostream& o, point const& p) { return o << p.x << ", " << p.y; } // a user-defined generator constexpr point my_generator(std::size_t curr, std::size_t total) { return {curr*40.0f/(total-1), curr*20.0f/(total-1)}; } int main() { constexpr auto first_array = generate_array<5>(my_generator); constexpr auto second_array = generate_array<10>(my_generator); std::cout << "first array: \n"; for(auto p : first_array) { std::cout << p << '\n'; } std::cout << "========================\n"; std::cout << "second array: \n"; for(auto p : second_array) { std::cout << p << '\n'; } } 

7 Comments

Wow - Thats a lot of code! Is that really the shortest way of doing this?
@nbubis This is a very general solution. If you narrow down / specify more precisely what you want, there might be a less verbose solution.
I think he usage example is exactly what I'm looking for, but that sure is a lot of extra code :)
@nbubis Short enough now? I realized you don't even need the accessors for this kind of thing.
+1 here's what I got before reading your question (I updated it to make the make_integer_sequence opaque to callers, like you did, tnx for that idea)
|
1

Since C++14 you can use constexpr functions which can handle functions that aren't too fancy. This may be simpler than any template and struct stuff.

#include <array> #include <iostream> constexpr int N = 100; constexpr std::array<int, N> get_array() { std::array<int, N> arr{0}; for (int i=0; i<N; ++i) arr[i] = i*i; return arr; } int main() { constexpr auto arr = get_array(); for (int n : arr) std::cout << n << '\n'; } 

The lookup table is stored in the compiled binary, which you can see in Compiler Explorer.

In C++20 you can even have a std::vector inside a constexpr, whereas previously you had to have an array allocated with a max size known ahead of time. However, we can't make anything dynamically allocated becom constexpr, so the below wouldn't work:

constexpr std::vector vec = compute(); 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.