不同参数类型的可变参数模板函数的递归

时间:2017-10-19 19:34:36

标签: c++ arguments variadic-templates variadic-functions

考虑以下代码

template <int INDEX>
void foo() {  } // termination version

template <int INDEX, typename Arg, typename... Args>
void foo(Arg head, Args... args) {

    if (INDEX == 0) {


        cout << head << endl;
    }

   else {


        foo <INDEX-1 > (args...);

   }

}

int main() {

   foo<1> (1, 3.1415);

   return 0;
}

代码按预期编译并输出3.1415。

但是,以下简单代码编译正常但始终输出1.您对此有任何修复吗?

template <int INDEX>
void foo() {   } // termination version

template <int INDEX, typename Arg, typename... Args>
Arg foo(Arg head, Args... args) {
    if (INDEX == 0) {

        return head;

    }

    else {


        foo <INDEX-1 > (args...);

    }



}



 int main() {

 cout<<foo<1> (1, 3.1415,"Test!");

return 0;
}

换句话说,如何递归调用具有不同参数类型的可变参数模板化函数?

2 个答案:

答案 0 :(得分:1)

1您的方法出现问题

1.1 return

中缺少foo<1>

确保您了解嵌套调用中return的工作原理。您的foo<1>来电foo<0>会将其(foo<0>&#39; s)的第一个参数返回foo<1>。但是,foo<1>并不关心foo<0>的回复,因为它会像foo<0>这样调用:

else {
    foo<i-1>(args...);// `i-1` becomes `0`
}

编译器知道你在这里遇到问题:从foo<1>(已被忽略)返回后,foo<0>应返回哪个值?它必须返回与第一个参数相同类型的值,但它在到达其结束}之前永远不会返回。

正如评论中指出的那样,您应该打开编译器警告来检测这些问题。在这种情况下,-Wall (GCC documentation on warning options)sufficient for GCC and clang to warn you (online demo),但有更多警告可用。如果您的文件名为main.cpp,并且在第23行第1列中找到结束},则编译器警告可以读取

main.cpp: In function ‘Arg foo(Arg, Args ...) [with int INDEX = 1; Arg = int; Args = {double, const char*}]’:
main.cpp:23:1: warning: control reaches end of non-void function [-Wreturn-type]


 }
 ^

1.2必须在编译时知道返回类型

您可以尝试通过将foo<0>的返回值传递给堆栈来修复代码:

else {
    return foo<i-1>(args...);// NOTE: type of return value depends on `foo<i-1>`
}

但是,失败是因为foo<1>已声明返回与第一个参数相同类型的值:

template<int i, class Arg, class... Args>
Arg foo(Arg, Args... args) {// <--------- NOTE: must return a value of type `Arg`

2修复您自己的递归实现

2.1 C ++ 17及以上

使用C ++ 17,您可以使用auto作为返回类型和constexpr if来实现递归,如下所示:

template<size_t i, class T0, class... Ts>
auto foo(T0 v0, Ts... vs) {
  static_assert(i < 1u + sizeof...(Ts));
  if constexpr(0u == i) return v0;// <------ NOTE: must be `if constexpr` (C++17)
  else return foo<i-1u>(vs...);
}

2.2 C ++ 14及以上

使用C ++ 14,您还可以使用auto作为返回类型,但constexpr if不可用。解决方法是一个众所周知的习惯用法,并使用类模板的专门化来实现&#34;实现&#34;递归逻辑:

template<int i>
struct foo_impl {
  static_assert(i > 0, "the case `i == 0` requires a specialization");

  template<class T0, class... Ts>
  static auto get(T0, Ts... vs) {
    return foo_impl<i-1>::get(vs...);
  }
};

template<>
struct foo_impl<0> {
  template<class T0, class... Ts>
  static auto get(T0 v0, Ts...) {
    return v0;
  }
};

template<int i, class... Ts>
auto foo(Ts... vs) {
  static_assert(i >= 0 && i < sizeof...(Ts), "index range: [0, size)");
  return foo_impl<i>::get(vs...);// forward to "implementation"
}

2.3 C ++ 11及以上

使用C ++ 11,您需要指定尾随返回类型,这有点单调乏味。有关详细信息,请参阅max66's answer

3最终建议

  • 启用并分析编译器警告(-Wall是绝对最小值。)
  • 熟悉这些技巧后,请不要自己实现。相反,请学习和使用std::tuple等标准解决方案。
  • 谨慎使用编译时递归。它可能会显着增加您的编译时间。

答案 1 :(得分:0)

我认为不可能(至少在C ++ 11和C ++ 14中)开发这种类型的foo(),因为你不知道正确的退货类型。

如果您不想使用std::tuple,我建议开发一种类型特征来提取第n种类型并通过SFINAE管理foo()

以下是可能的解决方案

#include <iostream>
#include <type_traits>

template <std::size_t, typename...>
struct indexType
 { using type = int; }; // the type of the foo() without argument

template <std::size_t I, typename I0, typename ... Is>
struct indexType<I, I0, Is...>
 { using type = typename indexType<I-1U, Is...>::type; };

template <typename I0, typename ... Is>
struct indexType<0U, I0, Is...>
 { using type = I0; };

template <std::size_t I, typename ... Args>
using indexType_t = typename indexType<I, Args...>::type;


template <std::size_t>
int foo ()
 { return 0;  } // termination version: a return type is needed


template <std::size_t I, typename Arg, typename... Args>
auto foo (Arg const & head, Args const & ...)
   -> typename std::enable_if<I == 0U, Arg>::type
 { return head; }

template <std::size_t I, typename Arg, typename... Args>
auto foo (Arg const &, Args const & ... args)
   -> typename std::enable_if<I != 0U, indexType_t<I-1U, Args...>>::type
 { return foo<I-1U>(args...); }

int main ()
 {
   std::cout << foo<1U> (1, 3.1415, std::string("Test!")) << std::endl;
   std::cout << foo<2U> (1, 3.1415, std::string("Test!")) << std::endl;
   std::cout << foo<3U> (1, 3.1415, std::string("Test!")) << std::endl;
 }