c++ Unpacking parameter pack from template arguments
Asked Answered
A

4

6

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).

Avrom answered 13/3, 2014 at 15:28 Comment(1)
you should specify C++ standard, since this changes level of complexity of the answer.Dubois
T
13

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;
}
Trochanter answered 13/3, 2014 at 16:4 Comment(1)
The last solution is simply beautiful.Caiaphas
S
9

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.

Subsonic answered 13/3, 2014 at 16:17 Comment(4)
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>();Avrom
It now works if change to std::array<bool, SIZE> b = {}; in func definitionAvrom
@Avrom I really thought that since std::array is an aggregate type, its elements are default-initialized. Anyhow, I've changed initialization to b = {};, thanks.Subsonic
Nice clean and works with C++11: godbolt.org/z/K4sKabz5vDubois
P
4

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;
}
Perichondrium answered 7/2, 2022 at 12:47 Comment(0)
S
1

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.

Subsonic answered 13/3, 2014 at 16:1 Comment(1)
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. +1Plage

© 2022 - 2024 — McMap. All rights reserved.