Variadic Function Template with Pack Expansion Not in Last Parameter

Variadic function template with pack expansion not in last parameter

Because when a function parameter pack is not the last parameter, then the template parameter pack cannot be deduced from it and it will be ignored by template argument deduction.

So the two arguments 0, 0 are compared against , int, yielding a mismatch.

Deduction rules like this need to cover many special cases (like what happens when two parameter packs appear next to each other). Since parameter packs are a new feature in C++11, the authors of the respective proposal drafted the rules conservatively.

Note that a trailing template parameter pack will be empty if it is not otherwise deduced. So when you call the constructor with one argument, things will work (notice the difference of template parameter pack and function parameter pack here. The former is trailing, the latter is not).

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.

Template parameter pack deduction when not passed as last parameter

We can block deduction:

template<typename... Args>
void testfunc(const block_deduction<std::function<void (float, Args..., char)>>& func)

with

template<class T>
struct tag_t{using type=T;};

template<class T>
using block_deduction=typename tag_t<T>::type;

and now Args... is in a non-deduced context.

You can do fancier things with SFINAE and omitting char then testing that char is at the end of the Args..., but that seems overkill.


I will bet dollars to donuts that when gcc and clang disagree with MSVC, MSVC isn't right. But I have not standard delved to confirm that.

Variadic template not printing the last parameter

The problem is that Visual C++ pulls in std::log into the global namespace.

Your chain of calls should be

log("Logging", 1, 2, 3.2, 4);
log(1, 2, 3.2, 4);
log(2, 3.2, 4);
log(3.2, 4);
log(4);
log();

The problem with Visual C++ is that the next to last call, log(4), is really std::log(4), which of course will not call your own log function.

The simplest solution is to rename your function as something else.

Parameters after parameter pack in function

Am I doing something wrong or is this prohibited by the language?

Deduction of parameter packs occurs only when the pack is the last argument.

[temp.deduct.type]/5.7:

The non-deduced contexts are:

  • [...]
  • A function parameter pack that does not occur at the end of the parameter-declaration-list.

So that's normal behavior of standard C++.

is there any way to get the syntax I'm trying to do?

Use the first syntax as a workaround:

   template<typename... T>
std::vector<int> SubscribeMultiple1(int callback, T&&... channels)

Do Variadic Template Parameters Always Have to be Last?

Variardic arguments don't have to be last -- but it doesn't help you.

Your error is in the recursive call, when you try to set begin to be something different than 0. In that line, the compiler cannot figure out that your begin is supposed to be the std::size_t parameter, and bails out.

This compiles fine even in gcc 5.1:

template <class... Tp, std::size_t begin = 0U>
auto foo(std::tuple<Tp...>& t) -> std::enable_if_t<begin == sizeof...(Tp), void> {
std::cout << '\n';
}

template <class... Tp, std::size_t begin = 0U>
auto foo(std::tuple<Tp...>& t) -> std::enable_if_t<begin < sizeof...(Tp), void> {
std::cout << '\n';
}

(I rewrote it to figure out where it was going wrong, so it is slightly different in unimportant ways).

The important way it differs it the lack of recursive call.

As an aside, your printing code is a bit awkward. Consider using something like for_each_arg:

template<class F, class...Args>
void for_each_arg(F&& f, Args&&...args) {
using discard=int[];
(void)discard{((
f(std::forward<Args>(args))
),void(),0)...,0};
}

either mix the above with std::apply or write your own:

namespace details {
template<class F, class Tuple, std::size_t...Is>
decltype(auto) apply( std::index_sequence<Is...>, F&& f, Tuple&& args )
{
return std::forward<F>(f)( std::get<Is>(std::forward<Tuple>(args))... );
}
}
template<class F, class Tuple>
decltype(auto) apply(F&& f, Tuple&& tuple) {
using dTuple = std::decay_t<Tuple>;
return details::apply(
std::make_index_sequence<std::tuple_size<dTuple>::value>{},
std::forward<F>(f),
std::forward<Tuple>(tuple)
);
}

template<class F, class Tuple>
decltype(auto) for_each_tuple_element( F&& f, Tuple&& tuple ) {
return apply(
[&](auto&&...args){
for_each_arg( std::forward<F>(f), decltype(args)(args)... );
},
std::forward<Tuple>(tuple)
);
}

and now you don't have a recursion depth equal to the number of elements in your tuple.

template <class Tuple>
void foo(Tuple&& tuple) {
for_each_tuple_element(
[](auto&& arg){ std::cout << decltype(arg)(arg); },
std::forward<Tuple>(tuple)
);
std::cout << '\n';
}

live example.

Optional argument after template parameter pack of supposedly known length

You could make it overloaded instead of having an optional argument. You'd need to move the "optional" to before the parameter pack though.

The second overload would then just forward the arguments to the first, with the "default" parameter set.

#include <iostream>

template <typename... T>
void foo(void(func)(T...), int opt, T... args)
{
std::cout << opt << '\n';
func(args...);
}

template <typename... T>
void foo(void(func)(T...), T... args)
{
return foo(func, 0, args...); // forwards with the default set
}

void bar(int, int) {}

int main()
{
foo(&bar, 1, 2); // prints 0
foo(&bar, 3, 1, 2); // prints 3
}

You might want to move the optional all the way to the first position to let the function and its parameters be together. It's a matter of taste.


Another option could be to exclude the optional parameter and only have the parameter pack and to extract the optional if it's present or use the default value if it's not.
This requires that you restrict the signature of func to match the function you aim to call.

#include <iostream>
#include <tuple>

template <class... T>
void foo(void func(int, int), T&&... args) {
int opt = [](T... args) {
if constexpr (sizeof...(T) > 2) return std::get<2>(std::tuple{args...});
else return 0; // use the default
}(args...);

std::cout << opt << '\n';

[&func](int a, int b, auto&&...) { func(a, b); }(args...);
}

void bar(int, int) {}

int main() {
foo(&bar, 1, 2); // prints 0
foo(&bar, 1, 2, 3); // prints 3
}

Building on the second version but giving a lot more freedom, you could introduce a separate parameter pack for func. If that pack has the same size as pack of arguments supplied, you need to pick a default value for opt. If it on the other hand contains more arguments than needed for the function, you can select which one of the extra arguments that should be used for opt. In the example below, I just picked the first extra parameter.

#include <iostream>
#include <tuple>
#include <type_traits>
#include <utility>

// a helper to split a tuple in two:
template <class... T, size_t... L, size_t... R>
auto split_tuple(std::tuple<T...> t,
std::index_sequence<L...>,
std::index_sequence<R...>)
{
return std::pair{
std::forward_as_tuple(std::get<L>(t)...),
std::forward_as_tuple(std::get<R+sizeof...(L)>(t)...)
};
}

template <class... A, class... T>
void foo(void func(A...), T&&... args) {
static_assert(sizeof...(T) >= sizeof...(A));

// separate the needed function arguments from the rest:
auto[func_args, rest] =
split_tuple(std::forward_as_tuple(std::forward<T>(args)...),
std::make_index_sequence<sizeof...(A)>{},
std::make_index_sequence<sizeof...(T)-sizeof...(A)>{});

int opt = [](auto&& rest) {
// if `rest` contains anything, pick the first one for `opt`
if constexpr(sizeof...(T) > sizeof...(A)) return std::get<0>(rest);
else return 0; // otherwise return a default value
}(rest);

std::cout << opt << '\n';

std::apply(func, func_args);
}

void bar(int a, int b) {
std::cout << a << ',' << b << '\n';
}

int main() {
foo(&bar, 1, 2); // prints 0 then 1,2
foo(&bar, 1, 2, 3, 4); // prints 3 then 1,2
}

Non-last parameter pack compilation error

I'm not really an expert but, as far I know...

func(1, 1, 1); // why doesn't this compile?

Because, in a function template, the types of a parameter pack can be deduced only if it's in last position.

The first call

func<int, int, int>(1, 1, 1);

works because the types of the parameter aren't deduced but are explained (Args... is int, int, int) and func() receive a fourth int (with default value zero)

The call

func<int, int>(1, 1, 1);

works also because Args... is explained as int, int and the func() function receive a third int with not default value 1


sum(1, 1); // why doesn't this compile?

Same reason: the parameter pack isn't in last position so can't be deduced; but works

sum<int, int>(1, 1);

because T is explicated as int and Rest... as int also.


sum<int, int, int>(1, 1, 1); // why doesn't this compile while func<int, int, int>(1, 1, 1) does?

The fist level call works because T is explicated as int and Rest... as int, int; but sum<int, int, int>(1, 1, 1) call sum(rest...) that, in this case is sum(1, 1); it's sum(1, 1) that fail because Rest... isn't in last position so can't be deduced


// why are these compile? I only changed the order of the parameters compared to sum()
sum2(1);
sum2(1, 1);
sum2(1, 1, 1);

Because in sum2() the parameter pack list Rest... is in last position so can be (and is) deduced.

Best way to apply a void function to a parameter pack

This trick is the way to go pre-C++17, except that you need an extra , 0 in the array to support zero-length packs.

In C++17 and newer, use a fold expression: (f(args), ...);.


Note that you forgot perfect forwarding. You should be doing F &&f, Args &&... args, and then (f(std::forward<Args>(args)), ...);, and similarly for the first function.


I also question the value of having such a function. I'd understand doing this to a tuple, but if you already have a pack, you can do this manually at the call site.



Related Topics



Leave a reply



Submit