Expansion with Variadic Templates

Variadic template pack expansion

One of the places where a pack expansion can occur is inside a braced-init-list. You can take advantage of this by putting the expansion inside the initializer list of a dummy array:

template<typename... Args>
static void foo2(Args &&... args)
{
int dummy[] = { 0, ( (void) bar(std::forward<Args>(args)), 0) ... };
}

To explain the content of the initializer in more detail:

{ 0, ( (void) bar(std::forward<Args>(args)), 0) ... };
| | | | |
| | | | --- pack expand the whole thing
| | | |
| | --perfect forwarding --- comma operator
| |
| -- cast to void to ensure that regardless of bar()'s return type
| the built-in comma operator is used rather than an overloaded one
|
---ensure that the array has at least one element so that we don't try to make an
illegal 0-length array when args is empty

Demo.

An important advantage of expanding in {} is that it guarantees left-to-right evaluation.


With C++17 fold expressions, you can just write

((void) bar(std::forward<Args>(args)), ...);

Variadic template parameter pack expansion looses qualifier

The cv-qualifier should always be dropped (both in determining the type of dummyFunc and when substituting the deduced argument into the callFunc signature), and I'm pretty sure all compilers agree on this. It's not really what the question is about. Let's change the example a bit:

template <class... Args> struct S {
template <class... T> S(T...) {}
};

template<typename... Args>
void callFunc2(S<Args...>);

int main()
{
callFunc2<const int>(S<int>{});
}

Now GCC and Clang reject the code while MSVC accepts it.

GCC and Clang both have issues with the mismatch between const int (explicitly specified) and int (deduced) whereas MSVC is evidently happy just to let Args = [const int] as specified. Who is right?

As I see it the problem here is [temp.arg.explicit]/9, which states:

Template argument deduction can extend the sequence of template arguments corresponding to a template parameter pack, even when the sequence contains explicitly specified template arguments.

Thus, specifying an explicit template argument for the pack Args does not prevent deduction. The compiler still has to try to deduce Args in case it needs to be extended in order to match the function parameter types against the argument types.

It has never been clearly explained what this paragraph of the standard really means. I guess MSVC's approach could possibly be something like "deduce the pack as if there were no explicitly specified template arguments, then throw out the result if the explicitly specified template arguments are not a prefix of the deduced template arguments" which seems like a sensible way to handle this code. GCC and Clang might be something like "deduce the pack as if there were no explicitly specified template arguments, and then fail if the explicitly specified template arguments are not a prefix of the deduced template arguments" which would lead to the code being ill-formed, but this seems like an unfortunate interpretation because it's inconsistent with how explicitly specified template arguments are treated in non-variadic cases.

The example above is similar to a simplified version of the OP's example:

void dummyFunc(int);

template<typename... Args>
void callFunc(void(*)(Args...));

int main()
{
callFunc<const int>(&dummyFunc);
}

Here, the trailing Args&&... has been removed, which doesn't change the result: as with OP's code, Clang and MSVC accept it while GCC doesn't.. Only Clang has changed its opinion: it accepts this one while rejecting the one with S. To be fair, these two snippets are not really analogous: the one with S involves an implicit conversion. But it's not clear why Clang treats them differently.

From my point of view, GCC and Clang both have different bugs with variadic template deduction, while MSVC does the right thing in both cases. But it's hard to make an argument based on the standard text that this is unambiguously the case.

Variadic template: inline pattern expansion

If you fancy doing meta-programming yourself, you can accomplish the behaviour with four utility overloads.

Like another answer pointed out, the key is concatenating seq types, so our utilities will do just that. The important thing to understand is that meta-programming is primarily functional in nature, so the tools will be recursion and pattern matching (all during overload resolution). Here are the overloads:

namespace detail {
auto seq_concat() -> seq<>&;

template<typename... Args>
auto seq_concat(seq<Args...>&) -> seq<Args...>&;

template<typename... Args, typename... Brgs>
auto seq_concat(seq<Args...>&, seq<Brgs...>&) -> seq<Args..., Brgs...>&;

template<class Seq, class... Seqs>
auto seq_concat(Seq& s, Seqs&... ss) -> decltype(seq_concat(s, seq_concat(ss...)));
}

The first three are our "base case". When concatenating zero to two seqs, we lay out explicitly what type we should get, while the final overload is our general case. To concatenate a list of three or more "seq-like" types, is to concentrate the elements in the list tail, then take that result and concatenate it with the list head.

Now we can write the utility you need, because to create a seq<Other, A, Other, B> we may just concatenate seq<Other, A> with seq<Other, B>, and in if we need more types, we can do pack expansion on seq<Other, Args>... and concatenate all of those.

struct Other;
template<typename... Args>
using seq_interleave_with_other = typename std::remove_reference<decltype(detail::seq_concat(std::declval<seq<Other, Args>&>()...))>::type;

We create a pattern std::declval<seq<Other, Args>&>()... to "give us references to objects" we can use in an unevaluated context, then invoke detail::seq_concat(...) on the pattern. The return type is (almost) what we need, because our utilities added references for simplicity of implementing them. A quick trip through std::remove_reference and we are done. To use it, just write:

template<typename... Args>
struct MyStruct : seq_interleave_with_other<Args...>
{};

Here's everything in a live example to play with.

template template parameter expansion for variadic templates

It is not possible according to your first attempt, but it is possible according to your edit, where arguments are packed within std::tuple's. In this case, template Embed below takes arguments in each tuple and embeds them in Container.

See live example.

template<template<class... > class Container, typename P>
struct Embed_t;

template<template<class... > class Container, typename... T>
struct Embed_t <Container, std::tuple <T...> >
{
using type = Container <T...>;
};

template<template<class... > class Container, typename P>
using Embed = typename Embed_t <Container, P>::type;

template<template<class... > class Container, typename... P>
struct ContainerTemplate
{
using container = std::tuple<Embed <Container, P>...>;
};

In general, placing ... within ... is very tricky and can happen only in limited circumstances (I've only managed this once in a useful way).

C++ expand variadic template arguments into a statement

C++17 solution - fold expression:

template <typename... Ts>
auto anyCondition(Ts... xs)
{
return (xs || ...);
}

wandbox example


C++11 solution - for_each_argument:

template <typename TF, typename... Ts>
void for_each_argument(TF&& f, Ts&&... xs)
{
(void)(int[]){(f(std::forward<Ts>(xs)), 0)...};
}

template <typename... Ts>
auto anyCondition(Ts... xs)
{
bool acc = false;
for_each_argument([&acc](bool x){ acc = acc || x; }, xs...);
return acc;
}

wandbox example

I gave a talk about this snippet at CppCon 2015:

CppCon 2015: Vittorio Romeo “for_each_argument explained and expanded"

C++17 parameter pack expansion with function parameter evaluation

I think your solution (use of std::make_index_sequence/std::index_sequence to get indexes in right order) is a good one (and works also with C++14).

Starting from C++17 you can also use std::tuple/std::apply()

void convert2 (std::vector<int> const & cv)
{
std::size_t i{};

std::tuple t{ static_cast<Args>(cv[i++])... };
std::apply([=](auto ... args){ eval(args...); }, t);
}

but it's almost your std::make_index_sequence/std::index_sequence solution, wrapped by std::apply().

Parameter pack expansion for variadic class member (tuple or other)

You're on the right track with std::apply but the syntax is incorrect; the pack expansion needs to be outside the call to process_arg. Also, you don't need the variable success at all; you can use a fold-expression directly:

bool process1() const
{
return std::apply([](auto &&... v) {
return (process_arg(v) && ...);
}, args);
}

Here's a demo



Related Topics



Leave a reply



Submit