Search code examples
c++variadic-templatesinner-classessfinaetype-traits

std::enable_if cannot be used to disable this declaration


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.


Solution

  • 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.