Arash
Arash

Reputation: 139

recursion in variadic template function of different argument types

consider the following piece of code

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

the code compiles and outputs 3.1415 as expected.

however, the following simple code compiles fine but always outputs 1. do you have any fix for this?

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

in other words, how can I recursively call a variadic templated function with different argument types?

Upvotes: 1

Views: 1666

Answers (2)

Julius
Julius

Reputation: 1861

1 Problems with your approach

1.1 Missing return in foo<1>

Make sure you understand how a return from a nested call works. Your foo<1> calls foo<0> which returns its (foo<0>'s) first argument back to foo<1>. But your foo<1> does not care about foo<0>'s return because it called foo<0> like this:

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

The compiler knows you have a problem here: Which value should foo<1> return after it got the return from foo<0> (which has been ignored)? It has to return a value of the same type as its first argument, but it never returns before reaching its closing }.

As pointed out in the comments, you should turn on compiler warnings to detect problems like these. In this case, -Wall (GCC documentation on warning options) is sufficient for GCC and clang to warn you (online demo), but there are more warnings available. If your filename reads main.cpp and the closing } is found line 23, column 1, the compiler warning could read

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 Return type must be known at compile time

You might attempt to fix your code by passing the return value from foo<0> up the stack:

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

However, that fails because foo<1> has been declared to return a value of the same type as its first argument:

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

2 Fix for your own recursive implementation

2.1 C++17 and above

With C++17 you can use auto as return type together with constexpr if to implement the recursion as follows:

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 and above

With C++14 you can also use auto as return type, but constexpr if is not available. The workaround is a well-known idiom and uses specialization of a class templates that "implements" the recursion logic:

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 and above

With C++11 you would need to specify trailing return types which is a bit tedious. See max66's answer for details.

3 Final recommendations

  • Enable and analyze compiler warnings (-Wall is an absolute minimum).
  • Once you are familiar with these techniques, do not implement this yourself. Instead, learn and use standard solutions like std::tuple.
  • Use compile-time recursion with caution. It may significantly increase your compilation time.

Upvotes: 1

max66
max66

Reputation: 66200

I don't think it's possible (in C++11 and C++14, at least) develop a foo() of this type because you don't know the correct return type.

If you don't want use std::tuple, I suggest to develop a type traits to extract the n-th type and manage foo() via SFINAE.

The following is a possible solution

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

Upvotes: 0

Related Questions