可变参数模板参数始终必须是最后一个

Do Variadic Template Parameters Always Have to be Last?

本文关键字:参数 最后一个 变参      更新时间:2023-10-16

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

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

在 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);
进度.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: 注意:模板参数推导/替换失败:
进度.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

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

问题不在于模板声明。这完全没问题:

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

变量参数不一定是最后一个 - 但它对你没有帮助。

您的错误在于递归调用,当您尝试将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';
}

活生生的例子。

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

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

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

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

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