Suppose you are using SFINAE like this to check if a type has either a T::bar
or a T::type
and either of them should be int
:
template <typename T>
typename std::enable_if<
std::is_same<typename T::bar,int>::value ||
std::is_same<typename T::type,int>::value, void>::type asdf(){ std::cout << "0";}
template <typename T>
typename std::enable_if<
!std::is_same<typename T::bar,int>::value &&
!std::is_same<typename T::type,int>::value, void>::type asdf(){ std::cout << "1";}
This won't work, because already std::is_same<...>
fails on substitution when T
has not both T::bar
and T::type
.
The solution is to use predicates that do not fail on substitution. Based on this answer, we can use the following detection idiom to detect if a type has some property:
// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;
// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};
// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};
Just for illustration, to detect if a type has T::type
:
template <typename T>
using has_type_t = typename T::type;
template <typename T>
using has_type = detect<T, has_type_t>;
Asking T::type
to be of a certain type is only a little more trickier:
template <typename X>
struct has_X_type_helper {
template <typename T>
using type = typename std::enable_if_t<std::is_same_v< typename T::type, X>,int>;
};
template <typename T,typename X>
using has_X_type = detect<T,has_X_type_helper<X>::template type>;
Type traits require a bit of boilerplate, and we have to write the same for T::bar
:
template <typename X>
struct has_X_bar_helper {
template <typename T>
using type = typename std::enable_if_t<std::is_same_v< typename T::bar, X>,int>;
};
template <typename T,typename X>
using has_X_bar = detect<T,has_X_bar_helper<X>::template type>;
Note that the traits has_X_type
and has_X_bar
do use SFINAE, but thats an implementation detail. The traits do not fail on substitution (unless their parameter fails already) but they evaluate either to std::true_type
or std::false_type
. Now the above SFINAE can be realized with ||
and &&
:
template <typename T>
typename std::enable_if<
has_X_type<T,int>::value ||
has_X_bar<T,int>::value, void>::type asdf(){ std::cout << "0";}
template <typename T>
typename std::enable_if<
!has_X_type<T,int>::value &&
!has_X_bar<T,int>::value, void>::type asdf(){ std::cout << "1";}
Live example @ godbolt
PS: What you call "quick and dirty" isn't dirty. It is quick, clean, and gets the job done. If you use predicates that evaluate to std::true_type
or std::false_type
then you can use plain boolean operators to construct more complicated predicates.