Best way to pass compile-time list of values to function?

  c++, c++20, compile-time, constexpr

I’m trying to find the best way to pass a compile-time list of values to a utility function, as an exercise based on a real use case. This list will be subjected to some series of operations and the resulting value will be used in another operation in runtime. Below are some solutions I found, simplified to MWEs.

Of course, the operations are much more complex in the real use case, hence the need for these utility functions.

Solution 1: Parameter pack

template <int number>
constexpr int sum() {
    return number;
}

template <int number, int next, int... rest>
constexpr int sum() {
    return number + sum<next, rest...>();
}

//API:

template <int... numbers>
inline void add(int& target) {
    target += sum<numbers...>();
}
...
int number = 0;
add<1, 2, 3, 4, 5>(number);

Pros:

  • Clean API
  • Needs only c++14

Cons:

  • Clunky implementation with recursion, painful to design and read when operations are complex

Solution 2: std::array

template <size_t N, std::array<int, N> numbers>
constexpr int sum() {
    int ret = 0;
    for (int number : numbers)
        ret += number;
    return ret;
}

//API:

template <size_t N, std::array<int, N> numbers>
inline void add(int& target) {
    target += sum<N, numbers>();
}
...
int number = 0;
add<5, std::array{1, 2, 3, 4, 5}>(number);

Pros:

  • Clean and readable implementation, easy to design no matter the complexity of operations

Cons:

  • Super clunky API, size of list must be specified separately
  • Needs c++20 to be able to pass inline std::array as non-type template parameter

Solution 3: std::array wrapper

template <size_t N>
struct IntArray {
    constexpr IntArray(std::array<int, N> arr_) : arr(arr_) {}
    const std::array<int, N> arr;
};

template <IntArray numbers>
constexpr int sum() {
    int ret = 0;
    for (int number : numbers.arr)
        ret += number;
    return ret;
}

//API:

template <IntArray numbers>
inline void add(int& target) {
    target += sum<numbers>();
}
...
int target = 0;
add<IntArray<5>({1, 2, 3, 4, 5})>(target);

Pros:

  • Clean and readable implementation, easy to design no matter the complexity of operations

Cons:

  • (Arguably) less but still clunky API, size of list must be specified separately
  • Needs c++20 to be able to pass inline IntArray as non-type template parameter, and also to be able to omit the IntArray template parameter value in at least the function definitions

Solution 4: std::initializer_list

template <std::initializer_list<int> numbers>
constexpr int sum() {
    int ret = 0;
    for (int number : numbers)
        ret += number;
    return ret;
}

template <std::initializer_list<int> numbers>
inline void add(int& target) {
    target += sum<numbers>();
}
...
int target = 0;
add<{1, 2, 3, 4, 5}>(target);

Pros:

  • Clean and readable implementation, easy to design no matter the complexity of operations
  • Clean, usable and readable API

Cons:

  • Doesn’t actually compile (g++ 10.3.0 with gnu++2a): ‘std::initializer_list<int>’ is not a valid type for a template non-type parameter because it is not structural

I have no idea what "not structural" means to be very honest. I’m actually surprised and disappointed by the fact that this approach doesn’t work, given that std::initializer_list is apparently fully constexpr and std::array works in the same situation. There seems to be a bug in the standard about the literalness of std::initializer_list though: https://stackoverflow.com/a/28115954/1525238 In any case, I do see this as a missed opportunity for some really cool compile-time wizardry.

The question:

Can you suggest any way to improve the solutions above in any way possible, or suggest other solutions?

Source: Windows Questions C++

LEAVE A COMMENT