c++ Unpacking parameter pack from template arguments

11,046

Solution 1

Recursive template solution:

// recursive helper struct
template <int n, int First, int ...Rest>
struct helper {
  static void funcImpl(std::array<bool, SIZE>& temp) {
    temp[First] = true;
    helper<n - 1, Rest...>::funcImpl(temp);
  }
};

// partial specialization to catch base case
template <int First>
struct helper<0, First> {
  static void funcImpl(std::array<bool, SIZE>& temp) {
    temp[First] = true;
  }
};

template <int ...Args>
std::array<bool, SIZE> func() {
    std::array<bool, SIZE> b = {}; // 0 inititalize array
    helper<sizeof...(Args) - 1, Args...>::funcImpl(b);
    return b;
}

EDIT: A super simplified version inspired by iavr's solution:

template <int... A>
std::array<bool, SIZE> func() {
    std::array<bool, SIZE> b = {};
    auto values = {A...};
    std::for_each(values.begin(), values.end(), [&](int n){b[n] = true;});
    return b;
}

Solution 2

Here is a much simpler solution that requires nothing extra, just this:

struct _do { template <typename... T> _do(T&&...) { } };

template <int... A>
std::array<bool, SIZE> func() {
    std::array<bool, SIZE> b = {};
    _do{b[A] = true...};
    return b;
}

This assumes the array is first initialized and then populated. My previous solution computed all values at compile time and directly initialized the array with them. So this is probably faster to compile and slower to run.

Solution 3

Since C++17, you can use fold expressions:

template <std::size_t... ARGS>
constexpr std::array<bool, SIZE> func() {
    std::array<bool, SIZE> b{};
    ((b[ARGS] = true), ...);    // fold expression
    return b;
}

Demo


For C++11 and 14, I'd prefer using an unnamed lambda, like this:

template <std::size_t... ARGS>
std::array<bool, SIZE> func() {
    std::array<bool, SIZE> b{};
    [](...){}(b[ARGS] = true...);
    return b;
}

Solution 4

See live example.

Here's the implementation of func:

template <int... A, int... N>
std::array<bool, sizeof...(N)>
func(sizes <N...>)
{
    return std::array<bool, sizeof...(N)>{{in <N, A...>()...}};
}

template <int... A>
std::array<bool, SIZE>
func() { return func <A...>(range <SIZE>()); }

where sizes represents an int sequence, range <S> constructs sequence 0,...,S-1 and in<N, A...>() checks whether number N is in sequence A... (definitions in the live example).

This not the most efficient (compile-wise) way to implement, because for every element of N... we need to scan pack A.... It's better to scan packs A..., L... in parallel, with a modification of function in(). But anyway this was more straightforward to think of and write down.

Share:
11,046
prestokeys
Author by

prestokeys

I'm an avid learner of C++ who was a total beginner back in 2013, and like to solve exercises that have not been solved anywhere before. Consequently, I am often musing over new questions or generalizing old questions. Usually, I can solve them on my own, but when I get stuck, or when the best solution I could come up with is sloppy or clearly much more complicated than necessary, I will find my way in stackoverflow. The experts here are so brilliant, and wish to join their ranks one day.

Updated on June 09, 2022

Comments

  • prestokeys
    prestokeys almost 2 years

    How to achieve want I want below? The paramater pack I want to unpack is not in a function argument list but template argument list.

    #include <iostream>
    #include <array>
    
    const std::size_t SIZE = 10;
    
    template <int...ARGS>
    std::array<bool, SIZE> func() {
        std::array<bool, SIZE> b;
        // I want to set b[n] = true, where n takes on all values from ARGS...
        // what to put in here???
        return b;
    }
    
    // Example of what I want to achieve:
    int main() {
        const std::array<bool, SIZE> b = func<1,3,7>();
        // I want b[1]==true, b[3]==true, b[7]==true, all others false
        for (int x: b) std::cout << x << std::endl;
    }
    

    I have to use this form for func (instead of func(1,3,7)) to get my bigger program working (I'm dealing with multiple inheritance issues).

  • prestokeys
    prestokeys about 10 years
    This potentially most elegant solution sets the true values correct, but the remaining are not set to false. I initialized as you suggested: std::array<bool, SIZE> b = {false}; b = func<1,3,7>();
  • prestokeys
    prestokeys about 10 years
    It now works if change to std::array<bool, SIZE> b = {}; in func definition
  • iavr
    iavr about 10 years
    @prestokeys I really thought that since std::array is an aggregate type, its elements are default-initialized. Anyhow, I've changed initialization to b = {};, thanks.
  • underscore_d
    underscore_d about 8 years
    Perhaps not the fastest at compile-time, but the ability to initialise arrays in a constexpr way (which either this or a very similar pattern would do) can be invaluable. +1
  • e.doroskevic
    e.doroskevic over 6 years
    The last solution is simply beautiful.
  • Marek R
    Marek R about 2 years
    Nice clean and works with C++11: godbolt.org/z/K4sKabz5v