While trying to use SFINAE to disable functions, have I created undefined behavior?

Viewed 142

I am trying to use SFINAE to disable certain functions of a class based on some non-templated enum arguments.

The following code does NOT compile with gcc, but appears to compile and work as expected when using the msvc compiler.

#include <iostream>
#include <type_traits>

enum class B { VARIANT1, VARIANT2 };

template<B B_VAL>
struct A {
    template<class = std::enable_if_t<B_VAL == B::VARIANT1>>
    void func1() {
        std::cout<<"VARIANT1"<<std::endl;
    }
    template<class = std::enable_if_t<B_VAL == B::VARIANT2>>
    void func2() {
        std::cout<<"VARIANT2"<<std::endl;
    }
};

int main()
{
    A<B::VARIANT1> a;
    a.func1();
}

The expected (and msvcs) behavior is that calling a function whose enable_if_t condition equates to false results in a compile time error, or the removal of the function candidate for overload resolution if an overloaded function was present in the example. In all other cases, the code should compile normally.

gcc on the other hand tells me that it can't find a type named "type" in "struct std::enable_if<false, void>" for the enable_if_t in the template of func2, which makes perfect sense as the member named "type" is only present in enable_if if the condition equates to true. But shouldn't this be the desired behavior for the SFINAE functionality and shouldn't the compiler ignore func2, as it is never called?

I now have three questions:

  1. As the two compilers produce different behavior, is it undefined and if yes, which parts/statements?

  2. Is SFINAE suited to achieve my goal, or have I misunderstood its use case?

  3. Would I be better off by using static asserts as alternative?

I am sorry if this question is a duplicate of this one, but I don't think that the answers there provided much help with my problem.

1 Answers

GCC is right. And it's because you aren't using SFINAE for your function. It may appear that you do because you employ utilities for SFINAE from the standard library, but there is a crucial ingredient missing here.

The 'S' in "SFINAE" stands for substitution. The substitution of template arguments into the parameters of a template we are trying to instantiate. Now, the template in question is func2. And for SFINAE to work, it is func2's argument that must fail to be substituted for its parameters. But here

std::enable_if_t<B_VAL == B::VARIANT2>

There is no parameter of func2 in use. It doesn't depend on anything that happens during substitution into func2. It's just an invalid type, completely independent of an attempt to actually instantiate func2.

It's not hard to fix though

template<B B_VAL_ = B_VAL, class = std::enable_if_t<B_VAL_ == B::VARIANT1>>
void func1() {
    std::cout<<"VARIANT1"<<std::endl;
}

template<B B_VAL_ = B_VAL, class = std::enable_if_t<B_VAL_ == B::VARIANT2>>
void func2() {
    std::cout<<"VARIANT2"<<std::endl;
}

Now, the check is against the substitution into the correct template.

Related