std::enable_if cannot be used to disable this declaration

Viewed 677

I have a quite complex piece of code which I simplified to this reproducer :

#include <type_traits>
#include <tuple>
template<typename ...As>
struct outer {
    template<typename ...Bs>
    struct inner {
        template<bool dummy, typename E = void>
        struct problem;
        using TA = std::tuple<As...>;
        using TB = std::tuple<Bs...>;

        template<bool dummy>
        struct problem<dummy, typename std::enable_if<std::tuple_size<TA>::value < std::tuple_size<TB>::value>::type>
        {
            static constexpr auto val() { return 1; } // actually a complex function
        };

        template<bool dummy>
        struct problem<dummy, typename std::enable_if<std::tuple_size<TA>::value >= std::tuple_size<TB>::value>::type>
        {
            static constexpr auto val() { return 0; }
        };
    };
};

int main() {
    return outer<int, float>::inner<double>::problem<false>::val();
}

it doesn't compile (with gcc or clang), saying :

<source>:13:82: error: failed requirement 'std::tuple_size<std::tuple<int, float> >::value < std::tuple_size<std::tuple<double> >::value'; 'enable_if' cannot be used to disable this declaration

        struct problem<dummy, typename std::enable_if<std::tuple_size<TA>::value <std::tuple_size<TB>::value>::type>

                                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~

<source>:27:31: note: in instantiation of template class 'outer<int, float>::inner<double>' requested here

    return outer<int, float>::inner<double>::problem<false>::val();

I tried some variants, and nothing works.

I read some already posted Q&A, such as : this one or this one but they don't seem to answer my question.

PS : I can use C++17, but that must work with any compiler.

3 Answers

Suggestion: try something as follows

struct inner {
    using TA = std::tuple<As...>;
    using TB = std::tuple<Bs...>;

    template<bool dummy, typename UA = TA, typename E = void>
    struct problem;

    template<bool dummy, typename UA>
    struct problem<dummy, UA,
       std::enable_if_t<(std::tuple_size_v<UA> < std::tuple_size_v<TB>)>>
     { static constexpr auto val() { return 1; } };

    template<bool dummy, typename UA>
    struct problem<dummy, UA,
       std::enable_if_t<(std::tuple_size_v<UA> >= std::tuple_size_v<TB>)>>
     { static constexpr auto val() { return 0; } };
};

I mean... take in count that SFINAE works with tests over template parameter of the struct/class (or function, or method) that you want enable/disable.

The problem in your original code is that the SFINAE test regards only TA and TB that are types defined in the inner struct that contain problem. So the test depends only from external template parameters (As... and Bs...), not from template arguments of problem.

Adding a template argument with default value for problem

// ..................VVVVVVVVVVVVVVVVV
template<bool dummy, typename UA = TA, typename E = void>
struct problem;

doesn't change the practical use of problem itself but transform the test in std::enable_if

template<bool dummy, typename UA>
struct problem<dummy, UA, // ..........VV  UA, not TA
   std::enable_if_t<(std::tuple_size_v<UA> < std::tuple_size_v<TB>)>>
 { static constexpr auto val() { return 1; } };

in a test that involve a template parameter of problem itself.

As @unimportant commented: if-constexpr since C++17. Allows you to get rid of dummy and many more lines:

#include <type_traits>
#include <tuple>

template<typename ...As>
struct outer {
    template<typename ...Bs>
    struct inner {
        using TA = std::tuple<As...>;
        using TB = std::tuple<Bs...>;

        static constexpr auto val() { 
            if constexpr (std::tuple_size_v<TA> >= std::tuple_size_v<TB>) {
                return 0;
            }
            else {
                return 1;
            }
        }
    };
};

int main() {
    return outer<int, float>::inner<double>::val();
}

The program '[4544] main.exe' has exited with code 0 (0x0).

As suggested in the comments, using if constexpr (part of C++17) makes the code way simpler:

template<typename ...As>
struct outer {
    template<typename ...Bs>
    struct inner {
        using TA = std::tuple<As...>;
        using TB = std::tuple<Bs...>;

        struct problem
        {
            static constexpr auto val()
            {
                if constexpr (std::tuple_size<TA>::value < std::tuple_size<TB>::value)
                    return 1; // Complex function goes here
                else
                    return 0; // Other complex function (?) goes here
            }
        };
    };
};

Demo

Related