user11112854
user11112854

Reputation:

Compile-time recursion with SFINAE as template argument

I was playing around with templates, and I wanted to use them to write a recursive function that will be evaluated during compile-time. And I want it to branch depending on the number that I pass to it. The function does have a constraint; I want to keep the return value.

So this was my attempt to write the function (it doesn't compile):

template<int n, typename std::enable_if_t<n==1>>
constexpr auto fun() { return std::make_tuple(1); }

template<int n, typename std::enable_if_t<n==2>>
constexpr auto fun() { return std::make_tuple(fun<1>(), 2); }

template<int n, typename enable = void>
constexpr auto fun() {
    return std::tuple_cat(fun<n-1>(), n);
}

int main() {
    constexpr auto x = fun<4>();

    return 0;
}

The issue that I'm facing is that I'm not sure where to put the std::enable_if_t statement, and how to write it exactly to assure that my function branches correctly. What am I missing here?

Upvotes: 0

Views: 224

Answers (2)

Timo
Timo

Reputation: 9835

Assuming you want to concatenate your tuple and create it in the form of

fun<4>() == tuple(1, 2, 3, 4);

you can write two templates like

template<int n, std::enable_if_t<n == 1>* = nullptr>
constexpr auto fun() 
{
    return std::make_tuple(1); 
}

template<int n, std::enable_if_t<n != 1>* = nullptr>
constexpr auto fun()
{
    return std::tuple_cat(fun<n-1>(), std::tuple(n));
}

However, that's not the "C++17 way" of doing this. It can be expressed much nicer by using if constexpr

template<int n>
constexpr auto fun2()
{
    if constexpr (n > 1)
        return std::tuple_cat(fun2<n-1>(), std::tuple(n));
    else
        return std::tuple(1);
}

Regarding sequence creations (things like 1, 2, 3, 4, ...) there is also std::integer_sequence which can be used in combination with template parameter packs

template <int... nums>
constexpr auto construct(std::integer_sequence<int, nums...>)
{
    return std::tuple((nums + 1)...);
}

template<int n>
constexpr auto fun3()
{
    return construct(std::make_integer_sequence<int, n>());
}

Here is a full example.

Upvotes: 2

max66
max66

Reputation: 66240

Not clear for me what do you exactly want but... let me guess: you want something as follows

#include <tuple>
#include <type_traits>

template<int n, std::enable_if_t<n==1, bool> = true>
constexpr auto fun() { return std::make_tuple(1); }

template<int n, std::enable_if_t<n!=1, bool> = true>
constexpr auto fun() { return std::tuple_cat(fun<n-1>(), std::make_tuple(n)); }

int main() { constexpr auto x = fun<4>(); }

The trick

template<int n, typename enable = void>

is a SFINAE trick for template class, where there is the main version and some specialization; so the main version define the signature and the specializations a SFINAE enabled/disabled through std::enable_if.

With functions you have overloading of functions but not partial specialization. You have to enable/disable every single overloaded function indipendently

template<int n, std::enable_if_t<n==1, bool> = true>
// ...

template<int n, std::enable_if_t<n!=1, bool> = true>
// ...

Observe that I have written

template<int n, std::enable_if_t<n==1, bool> = true>

and not

template<int n, typename = std::enable_if_t<n==1>>

The second form also works to enable/disable a single function but doesn't works when you have different functions with the same signature (as in your case) and you want enable only one version.

Upvotes: 1

Related Questions