Reputation: 139
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
Reputation: 1861
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]
}
^
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`
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...);
}
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"
}
With C++11 you would need to specify trailing return types which is a bit tedious. See max66's answer for details.
-Wall
is an absolute minimum).std::tuple
.Upvotes: 1
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