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

Time:11-19

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? Ideally the "best" solution would combine all of API and implementation cleanliness and readability, while requiring as low c standard as possible.

CodePudding user response:

Thanks to HolyBlackCat for suggesting to initialize a std::array from the pack, which I didn't know was possible:

template <int... numbers>
constexpr int sum() {
    std::array<int, sizeof...(numbers)> arr = {numbers...};
    int ret = 0;
    for(int number : arr)
        ret  = number;
    return ret;
}

//API:

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

This makes the implementation much cleaner. Requires c 17.

CodePudding user response:

Why don't you just take an initializer_list argument?

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

int target = sum({1, 2, 3, 4, 5});
  • Related