Do Variadic Template Parameters Always Have to be Last?

假装没事ソ 提交于 2019-12-12 18:39:21

问题


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?


回答1:


You are wrong -- variardic arguments doesn'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.




回答2:


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



回答3:


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.



来源:https://stackoverflow.com/questions/35701888/do-variadic-template-parameters-always-have-to-be-last

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!