Search code examples
c++templatessfinaenon-type-template-parameter

SFINAE using std::enable_if: type parameter vs non-type parameter


template<bool F, typename = std::enable_if_t<F>>
auto func1() -> int { return 0; }
template<bool F, typename = std::enable_if_t<!F>>
auto func1() -> int { return 0; }

template<bool F, std::enable_if_t<F, int> = 0>
auto func2() -> int { return 0; }
template<bool F, std::enable_if_t<!F, int> = 0>
auto func2() -> int { return 0; }

Here are two sets of overloaded functions: func1() and func2(). func1() uses type parameter for SFINAE and func2() uses non-type parameter for SFINAE.

When compiling these functions, func1() causes compilation error but func2() does not. Why does SFINAE work for func2() and fails for func1()?


Solution

  • Because in the first case, a SFINAE failure remove only the default for a template parameter.

    // in case F is true, you have 
    
    template <bool F, typename = void>
    int func1() { return 0; }
    
    template <bool F, typename> // no more default  but still available
    int func1() { return 0; }
    

    So doesn't disable the function, and you have two definitions of the same function (a default value doesn't change a function signature) so, as pointed by Jarod42 (thanks), you have a violation of the One Definition Rule.

    In the second case you remove a template parameter, so you destroy the function, so no collision anymore.

    template <bool F, int = 0>
    int func2() { return 0; }
    
    // template<bool F, ...>  // function removed
    // int func2() { return 0; }
    

    You can verify that, in the first case, also the "disabled" function is still available, with a test with a single function

    template <bool F, typename = std::enable_if_t<F>>
    int foo ()
     { return 0; }
    

    and calling it with true, false and false, void.

    foo<true>();  // compile: foo<true, void>() called
    foo<false>();  // compilation error: no second template paramenter
    foo<false, void>();  // compile: explicit second template parameter