user1810087
user1810087

Reputation: 5334

recursive variadic template can't deduce argument

I have a recursive function template where type 'T' is used internally but the variadic template argument is used only for generate the recursive template.

int qi(int t) {
    return 0;
}

template <typename T, typename... U>
int qi(int t) {
    //do stuff...
    return qi<U...>(t); // <--- error here
}

If I try to compile it, I get the error could not deduce template argument for 'T'. Why can't the function be deduced to the non template variant of qi (int qi(int t){}) ?

Upvotes: 2

Views: 450

Answers (2)

Aaron McDaid
Aaron McDaid

Reputation: 27133

First, declare qi to take any number of template arguments, including zero arguments.

template <typename... TU>
extern
int qi(int t);

We must eventually provide an implementation of this. But first, we are allowed to provide any number of fully-specialized (not partially-specialized) templates. So, we implement the zero-argument one:

template < > 
int qi< >(int ) { 
    return 0;
}

It's a specialization (because there is a < after qi) and a full specialization (because the < > after template is empty.

We would like to implement the non-specialized qi template now. We're not allowed to do any partial specialization, and we don't want to do full specialization as that would mean having to implement every set of types you might use. So that means we must implement something with this signature: template <typename... TU> int qi(int t) ...

We're stuck with TU, and no obvious way to 'peel off' the T to be left with the pack U.... The simplest thing is to pass off the implementation to a helper function:

template<typename T, typename... U>
int qi_helper(int t) {
    // do any other interesting stuff
    return qi<U...>(t); 
}

template <typename... TU>
int qi(int t) {
    // just pass on the call to qi_helper
    return qi_helper<TU...>(t);
}

Upvotes: 3

Barry
Barry

Reputation: 302852

The problem you're running into is that qi<>(int ) and qi(int ) are not the same call - the former is still a template, so the overload you attempted to write as your base case actually isn't. It's actually worse since the initially obvious solution:

template <>
int qi(int ) { ... }

since won't work since that isn't an explicit specialization of your qi (since your function template takes at least one type). The simplest thing would be to actually use arguments to overload with:

template <typename...> struct sequence { };

template <typename... T>
int qi(int t) { return qi_helper(t, sequence<T...>{} ); }

With:

template <typename T, typename... U>
int qi_helper(int t, sequence<T, U...> ) {
    // do stuff
    return qi_helper(t, sequence<U...>{}); 
}

int qi_helper(int t, sequence<> ) {
    // base case
}

You can accomplish a similar thing with a struct QiHelper<T...> that is specialized for the two cases (since you can partially specialize a class template).

If you didn't want to return anything, you could forward to a helper like this:

template <typename T> void qi_helper(int t) {
    /* logic for single type */
}

template <typename... T>
void qi(int t) {
    using swallow = int[];
    (void)swallow{0,
        (void(qi_helper<T>(t)), 0)...
    };
}

Depending on what you actually want to return, that may or may not still be possible.

Upvotes: 3

Related Questions