Detects incorrect usages of std::enable_if that don’t name the nested type type.

In C++11 introduced std::enable_if as a convenient way to leverage SFINAE. One form of using std::enable_if is to declare an unnamed template type parameter with a default type equal to typename std::enable_if<condition>::type. If the author forgets to name the nested type type, then the code will always consider the candidate template even if the condition is not met.

Below are some examples of code using std::enable_if correctly and incorrect examples that this check flags.

template <typename T, typename = typename std::enable_if<T::some_trait>::type>
void valid_usage() { ... }

template <typename T, typename = std::enable_if_t<T::some_trait>>
void valid_usage_with_trait_helpers() { ... }

// The below code is not a correct application of SFINAE. Even if
// T::some_trait is not true, the function will still be considered in the
// set of function candidates. It can either incorrectly select the function
// when it should not be a candidates, and/or lead to hard compile errors
// if the body of the template does not compile if the condition is not
// satisfied.
template <typename T, typename = std::enable_if<T::some_trait>>
void invalid_usage() { ... }

// The tool suggests the following replacement for 'invalid_usage':
template <typename T, typename = typename std::enable_if<T::some_trait>::type>
void fixed_invalid_usage() { ... }

C++14 introduced the trait helper std::enable_if_t which reduces the likelihood of this error. C++20 introduces constraints, which generally supersede the use of std::enable_if. See modernize-type-traits for another tool that will replace std::enable_if with std::enable_if_t, and see modernize-use-constraints for another tool that replaces std::enable_if with C++20 constraints. Consider these newer mechanisms where possible.