Shredd0r
Shredd0r

Reputation: 21

Recursive template call curiously not possible with SFINAE

EDIT: Problem solved, it was just wrong function declaration order.

when trying to solve a problem which seemed to be quite trivial at first glance, I stumbled over behaviour I can not explain.

I want to process an arbitrary int-array recursively and of course I have to stop the recursion somehow. As partial specialisation to concrete numbers is not possible with template functions (like

template<typename T, int N> foo<T, 0>(void)

), I tried to fake this with SFINAE. But when I want to call the second SFNIAE-function from the first one, I get a compiler error. Complete code example:

#include <algorithm>
#include <iostream>

using namespace std;

// -------------------------------------------------------------
template<typename T, int N>
void foo(T const& param, typename enable_if<N != 0, int>::type* = 0)
{
    cout << "recursive step " << N << endl;

    /* --- This was, what I desired: --- */
    //foo<T, N - 1>(param);

    /* --- THIS IS CAUSING AN ERROR! --- */
    foo<T, 0>(param);
}

// -------------------------------------------------------------
template<typename T, int N>
void foo(T const& param, typename enable_if<N == 0, int>::type* = 0)
{
    cout << "finish recursion" << endl;
}


// =============================================================
int main()
{
    int a[5] = {0, 1, 2, 3, 4};
    foo<decltype(a), 5>(a);

    /* --- SAME CALL AS WITHIN foo(), BUT CAUSING NO ERROR! --- */
    foo<decltype(a), 0>(a);
}

The compiler tells me: main.cpp:9: Fehler: no type named 'type' in 'struct std::enable_if' So it seems he somehow cannot solve for the second function. However, if I call the function from main(), its not a problem.

Its my first time with SFINAE, I hope I made no trivial mistakes. Thanks to everyone who read this far!

Upvotes: 2

Views: 469

Answers (2)

Holt
Holt

Reputation: 37626

You need to switch the order of the definition (or declare the trivial case before the normal case), the normal case does not see the trivial one so it cannot call it:

template<typename T, int N>
void foo(T const& param, typename enable_if<N == 0, int>::type* = 0) {
    cout << "finish recursion" << endl;
}

template<typename T, int N>
void foo(T const& param, typename enable_if<N != 0, int>::type* = 0) {
    cout << "recursive step " << N << endl;
    foo<T, N - 1>(param);
}

Note that you could use a helper class here to avoid this enable_if (and also change the signature so that you could let the compiler deduce parameters):

template<typename T, int N>
struct foo_h {
    static void call(T const& param) {
        foo_h<T, N - 1>::call(param);
    }
};


template<typename T>
struct foo_h<T, 0> {
    static void call(T const& param) {

    }
};

template<typename T, int N>
void foo(const T (&param)[N]) {
    foo_h<const T[N], N>::call(param);
}

Then:

int arr[] = {1, 2, 3, 4, 5};

foo(arr); // Automatic template parameters deduction!

Upvotes: 1

Quentin
Quentin

Reputation: 63144

I'm pleasantly surprised with Clang's helpfulness on this one:

main.cpp:14:5: error: call to function 'foo' that is neither visible in the template definition nor found by argument-dependent lookup
    foo(param);
    ^
main.cpp:29:5: note: in instantiation of function template specialization 'foo' requested here
    foo(a);
    ^
main.cpp:19:6: note: 'foo' should be declared prior to the call site
void foo(T const&, typename std::enable_if::type* = 0)
     ^

I mean, it's just one step away from logging in on Stack Overflow and giving the answer itself. Anyway.

Add the declaration of the second function above the first one, so you can call it from therein:

template<typename T, int N>
void foo(T const&, typename std::enable_if<N == 0, int>::type* = 0);

Note that you must remove the default argument from the definition as well.

Upvotes: 1

Related Questions