Variadic模板参数总是必须是最后的?

时间:2016-02-29 14:19:21

标签: c++ templates parameters variadic-templates

我是否总是必须在模板参数的末尾放置可变参数模板参数?

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

例如,我得到了各种错误:

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

run on gcc 5.1给我时:

  

prog.cpp:实例化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:从这里要求
  prog.cpp:15:23:错误:没有匹配函数来调用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:注意:候选人: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:注意:模板参数扣除/替换失败:
  prog.cpp:13:42:注意:候选人: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:注意:模板参数扣除/替换失败:

当参数换成:

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

程序运行正常:http://ideone.com/SozUbb

如果真的要求可变参数模板参数是最后的,有人可以给我一个关于这些信息的来源吗?

3 个答案:

答案 0 :(得分:7)

问题不在于模板声明。这非常好:

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

问题是这个电话:

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

虽然可以在参数包之后提供默认模板参数,但您无法在以后实际设置它。编译器无法知道包的结束位置以及包开始后的参数。

你应该翻转排序,将begin作为第一个参数,默认为:

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

这样您的递归调用可以是:

foo<begin + 1>(t);

答案 1 :(得分:3)

你错了 - 变异论证并不是最后的 - 但它对你没有帮助。

当您尝试将begin设置为与0不同时,您的错误就在递归调用中。在那一行中,编译器无法确定您的begin应该是std::size_t参数,并且罢工。

即使在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';
}

(我重写了它以弄清楚它出错的地方,所以它在不重要的方面略有不同)。

它与重要的不同之处在于缺乏递归调用。

顺便说一句,您的打印代码有点尴尬。考虑使用类似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};
}

将上述内容与std::apply混合或自行编写:

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

现在你的递归深度不等于元组中元素的数量。

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 :(得分:1)

根据标准§14.1/ 11模板参数[temp.param]:

  

如果主类模板或别名模板的模板参数是模板参数包,则它应该是最后一个模板参数。除非可以从参数类型列表中推导出该模板参数,否则函数模板的模板参数包不应该跟随另一个模板参数   函数模板或具有默认参数。

因此,您的设置是正确的,因为可变参数后跟默认模板参数。但是,您有语法错误,您应该更改为:

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

也就是说,在模板参数列表中...必须在Tp之前。