einpoklum
einpoklum

Reputation: 131636

Transform a variadic pack of types into values?

I have some function

template<typename T>
constexpr Foo make_foo<T>();

which essentially maps types to instances of Foo with no side-effects.

Now, I want to write the function magic,

template<typename Types...>
vector<Foo> magic();

which does the same as make_foo, but for variadic parameter packs; and in a way which it would then be easy for me to, say, stream all these Foo's to std::cout, or to iterate over them in a loop etc. I realize this question is not entirely well-defined, since I'm not clear if the relevant output I'm looking for is somekind of variadic packs of values (seeing how that doesn't exist in runtime).

So, what's the idiomatic way of doing that? I noticed that Eric Niebler has a blog page about a metaprogramming library which seems to be relevant, but it seems like a bit overkill. For my case.

Upvotes: 1

Views: 357

Answers (2)

The standard way to access individual members of a parameter pack is via recursion, stripping off one or more members out of a time. In this case, we could make magic() a wrapper function, that passes the parameter pack to a worker function that in turn calls make_foo<T>(). So, assuming make_foo<T>() can only take a single type at a time, we would have something like this.

// Prototypes:
// -----
template<typename T>
constexpr Foo make_foo();

template<typename... Types>
std::vector<Foo> magic();

template<>
std::vector<Foo> magic<>();

template<typename T>
void magic_worker(std::vector<Foo>& vec);

template<typename T, typename U, typename... Types>
void magic_worker(std::vector<Foo>& vec);

// Actual code:
// -----
// The wrapper.
template<typename... Types>
std::vector<Foo> magic() {
    std::vector<Foo> vec;

    magic_worker<Types...>(vec);

    return vec;
}

// Empty parameter pack.
template<>
std::vector<Foo> magic<>() {
    return std::vector<Foo>(0);
}

// Only one type left.
template<typename T>
void magic_worker(std::vector<Foo>& vec) {
    vec.push_back(make_foo<T>());
}

// At least two types left.
template<typename T, typename U, typename... Types>
void magic_worker(std::vector<Foo>& vec) {
    vec.push_back(make_foo<T>());
    magic_worker<U, Types...>(vec);
}

Not sure if this is the most efficient way to do what you want, but it should work. You can output the vector to std::cout via a range-based for loop, if you so desire.

std::vector<Foo> vec = magic</* args */>();

for (auto& a : vec) {
    std::cout << a;
}

For a working example, see here.


Edit: If you're wondering why the recursive version of magic_worker() has two distinct template parameters before the pack, it's to remove ambiguity. As a template pack can have zero members, having one version take <typename T> and the other take <typename T, typename... Types> would confuse the compiler, and prevent your code from compiling. See here for more info.

Upvotes: 0

T.C.
T.C.

Reputation: 137315

If you need a vector of Foos, then it's just

template<typename Types...>
vector<Foo> magic(){
    return { make_foo<Types>()... };
}

You can also throw them into a local array and play with them however you want:

Foo foos[] = { make_foo<Types>()... };

Upvotes: 1

Related Questions