3

Do I always have to place variadic template parameters at the end of my template parameters?

template <size_t begin = 0U, typename... Tp>
void foo(tuple<Tp...> t);

For example I get all kinds of errors with this:

#include <functional>
#include <iostream>
#include <string>
#include <tuple>
using namespace std;

template <typename... Tp, size_t begin = 0U>
enable_if_t<begin == sizeof...(Tp), void> foo(tuple<Tp...>& t){
    cout << endl;
}

template <typename... Tp, size_t begin = 0U>
enable_if_t<begin < sizeof...(Tp), void> foo(tuple<Tp...>& t) {
    cout << get<begin>(t) << ' ';
    foo<Tp..., begin + 1>(t);
}

int main() {
    tuple<int, string, float> t = make_tuple(42, "Jonathan Mee", 13.13);

    foo(t);
}

When run on gcc 5.1 gives me:

prog.cpp: In instantiation of std::enable_if_t<(begin < sizeof... (Tp)), void> foo(std::tuple<_Elements ...>&) [with Tp = {int, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, float}; unsigned int begin = 0u; std::enable_if_t<(begin < sizeof... (Tp)), void> = void]:
prog.cpp:21:7: required from here
prog.cpp:15:23: error: no matching function for call to foo(std::tuple<int, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, float>&)
foo<Tp..., begin + 1>(t);

prog.cpp:8:43: note: candidate: template<class ... Tp, unsigned int begin> std::enable_if_t<(begin == sizeof... (Tp)), void> foo(std::tuple<_Elements ...>&)
enable_if_t<begin == sizeof...(Tp), void> foo(tuple<Tp...>& t){

prog.cpp:8:43: note: template argument deduction/substitution failed:
prog.cpp:13:42: note: candidate: template<class ... Tp, unsigned int begin> std::enable_if_t<(begin < sizeof... (Tp)), void> foo(std::tuple<_Elements ...>&)
enable_if_t<begin < sizeof...(Tp), void> foo(tuple<Tp...>& t) {

prog.cpp:13:42: note: template argument deduction/substitution failed:

When the arguments are swapped to:

template <size_t begin = 0U, typename... Tp>
void foo(tuple<Tp...> t);

The program runs correctly: http://ideone.com/SozUbb

If this is really a requirement that variadic template parameters be last can someone give me a source on this information?

Barry
  • 286,269
  • 29
  • 621
  • 977
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288

3 Answers3

7

The problem isn't the template declaration. This is perfectly fine:

template <typename... Tp, size_t begin = 0U>
void foo(tuple<Tp...> t);

The problem is this call:

foo<Tp..., begin + 1>(t);

While you can provide a defaulted template argument after a parameter pack, you have no way of actually setting it later. The compiler has no way of knowing where the pack ends and the argument after the pack begins.

You should flip the ordering to put begin as the first argument, defaulted:

template <size_t begin = 0U, typename... Tp>
void foo(tuple<Tp...> t);

so that your recursive call can be:

foo<begin + 1>(t);
Barry
  • 286,269
  • 29
  • 621
  • 977
3

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.

Catskul
  • 17,916
  • 15
  • 84
  • 113
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I'm having trouble following what you're doing in `for_each_arg`. Can you explain that? – Jonathan Mee Feb 29 '16 at 15:02
  • @JonathanMee I create an array of integers, all zero. While doing so, I pack-expand an expression that invokes `f` on each index `Is` of the `Tuple` `args`. [Here](https://stackoverflow.com/questions/28263000/correct-usage-of-for-each-arg-too-much-forwarding) is a discussion of a similar `for_each_arg` that uses `std::initalizier_list` instead of an array. – Yakk - Adam Nevraumont Feb 29 '16 at 15:10
  • 2 questions after having read through your links. 1) Why cast your `int[]` to `void` 2) in the `int[]`'s intialization, how does the compiler know to repeat all of `((f(forward(args))), void(), 0)`? – Jonathan Mee Feb 29 '16 at 15:59
  • @jon `(void)` can dismiss some warnings on some compilers (unused temporary or somesuch). The `...` does the pack expand on the previous expression, in `()` in this case. Note the awkwardly placed `((` and `)` and `)`: written that way to highlight the non-boilerplate-line of the technique. – Yakk - Adam Nevraumont Feb 29 '16 at 16:03
  • OK, so we're creating an `int[]` which will never be used in order to call `f` `sizeof...(Args)` times. Fine, then why the two comma operators in the repeated parenthesis, wouldn't we just need `, 0` not `, void(), 0`? And why do we add one more 0 to the array than there are elements in `args`? – Jonathan Mee Feb 29 '16 at 16:11
  • 1
    @JonathanMee 1) defend against overloaded comma operators and 2) handle the case where we have an empty pack by ensuring that the array has at least one element. – T.C. Feb 29 '16 at 17:15
  • 2
    @T.C. aka: 1) to defend against evil, 2) do defend against nothing. – Yakk - Adam Nevraumont Feb 29 '16 at 17:53
  • @JonathanMee So, the thing is, in generic code, you gotta be conservative and deal with edge cases. You have to examine every expression and think "what are the possible overloads?". You have to deal with empty parameter packs, and million-sized parameter packs. The possibility that the return value of `f` on the argument returns a type with an overloaded comma operator is handled by doing a `f(blah), void()` -- you cannot overload with `void()`. The trailing zero deals with the fact that zero-sized arrays are illegal, and an empty pack would otherwise attempt to create a zero-sized array. – Yakk - Adam Nevraumont Feb 29 '16 at 17:56
  • @Yakk Yikes, right when I finally understood this you changed, `invoke` for `forward`. How does that work? – Jonathan Mee Mar 08 '16 at 14:11
  • @JonathanMee Can you be more specific? :) `for_each_tuple_element` takes a function and a tuple, and invokes the function on each element. `apply` is an implementation of `std::apply`, which takes a function and a tuple, and invokes the function with the contents of the tuple. `for_each_arg` takes a function and a pack of arguments, and invokes the function on each argument. So `for_each_tuple_element(f,tup)` is `apply(for_each_arg(f,...), tup)`? – Yakk - Adam Nevraumont Mar 08 '16 at 14:25
  • @Yakk I mean I think I get what you're saying, but why couldn't I just do: `experimental::apply([](auto& i) { cout << i; }, make_tuple(3.14, "Hello World!", -1));` – Jonathan Mee Mar 08 '16 at 14:54
  • 1
    @JonathanMee `apply( f, make_tuple(3.14, "Hello World!", -1))` calls `f(3.14, "Hello World!", -1)` – Yakk - Adam Nevraumont Mar 08 '16 at 15:04
  • @Yakk And it finally clicks. Thank you. – Jonathan Mee Mar 08 '16 at 15:12
  • @Yakk A couple more questions about `for_each_arg`: 1) Why does the compiler require us to do the `using discard = int[]` statement? 2) Why are you `forward`ing the values passed to `f`? I have a more in depth question on this as well, but I've posted that here: http://stackoverflow.com/q/35893826/2642059 – Jonathan Mee Mar 09 '16 at 14:12
  • @JonathanMee 1) It doesn't? It makes it clear we are discarding this value. 2) Why wouldn't I forward arguments? I see no reason to discard l/r valueness of the argument references. – Yakk - Adam Nevraumont Mar 09 '16 at 14:45
  • @Yakk So when I try doing: `template void for_each_arg(F&& f, Args&&...args) {static_cast(int[]{((f(std::forward(args))), void(), 0)..., 0});}` I get the error: "expected primary-expression before `int`: `static_cast(int[]{((f(std::forward(args))), void(), 0)..., 0});`": http://ideone.com/QktBcc – Jonathan Mee Mar 09 '16 at 14:53
1

According to the standard §14.1/11 Template parameters [temp.param]:

If a template-parameter of a primary class template or alias template is a template parameter pack, it shall be the last template-parameter. A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list of the function template or has a default argument.

Thus your setting is correct since variadic argument is followed by a default template argument. However, you have a syntax error, you should change to:

template <typename... Tp, size_t begin = 0U>
                  ^^^^^^
void foo(tuple<Tp...> t);

That is, in the template argument list ... must preceed Tp.

101010
  • 41,839
  • 11
  • 94
  • 168
  • It says an: "A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list of the function template or has a default argument." Isn't that saying that defaulted template parameters like mine should be able to follow a varadic template parameter? – Jonathan Mee Feb 29 '16 at 14:25
  • This is true, but isn't the issue. – Barry Feb 29 '16 at 14:41