0

Say, I have this return type definition for my method (template and method are irrelevant hence not shown), which requires handling logical "OR" for enable_if predicates:

typename std::enable_if<{predicate_1} or {predicate_2}), void>::type>
my_method(...) { ... }

Naturally it won't work, as once either of predicate substitution fails this instance will be scrubbed.

The first quick and dirty solution that springs on my mind is to come up with a definition which evaluates a failed predicate to false, e.g., like this:

template<bool, typename T = void>
struct failed_2_false: std::false_type {};
template<typename T>
struct failed_2_false<true, T>: std::true_type {};

then this would work:

typename std::enable_if<failed_2_false<{predicate_1}>::value or failed_2_false:<{predicate_2}>::value,
                        void>::type>
my_method(...) { ... }

so, my question is: - what / is there a standard (STL) idiomatic way to handle the logical "OR" for predicates?

Dmitry
  • 1,188
  • 1
  • 3
  • 12
  • [`std::disjunction`](https://en.cppreference.com/w/cpp/types/disjunction)? – Cory Kramer Jun 08 '20 at 14:59
  • 1
    I am a bit confused. Isnt it `enable_if` what fails, not the predicate? I mean if substitution already fails for the predicate you don't need `enable_if`? I might be wrong, I am used to have parameters for `enable_if` that don't fail but either evaluate to `true` or `false` – 463035818_is_not_a_number Jun 08 '20 at 15:11
  • 1
    can you include an example? – 463035818_is_not_a_number Jun 08 '20 at 15:13
  • @idclev463035818, well, first fails the predicate which makes `enable_if` to fail. A simple solution (besides shown) would be to have 2 instances of the method with each predicate along in `enable_if`. But that would be a code duplication and waste of space. Much nicer is to combine both predicates using logical `OR` junction under `enable_if` (the shown solution **does** work) – Dmitry Jun 08 '20 at 15:24
  • @CoryKramer, thanks, if you care to publish an example showing how `std::disjunction` would fit in my example, I'll approve your answer :) – Dmitry Jun 08 '20 at 15:26
  • sorry I dont understand what you mean. I know predicates that evaluate to `false` or `true`, but I do not find it "natural" that a predicate fails on substitution. If you write predicates that dont fail but evaluate to `false` instead then there is no problem – 463035818_is_not_a_number Jun 08 '20 at 15:27
  • acutally what you have as the solution that you don't want is how I would write the predicates in the first place – 463035818_is_not_a_number Jun 08 '20 at 15:30
  • @idclev463035818, the predicate undergoes a substitution which is also prone to failure. e.g. the predicate could be `std::is_same<...>` If such predicates fails substitution - it does not evaluate to `false` - if fails the whole instance. The shown wrapper indeed makes such trait predicate to evaluate to `false` upon failure, but then the question - what's the idiomatic way of doing it? – Dmitry Jun 08 '20 at 15:30
  • `std::is_same<...>::value` is either true or false. Sorry I dont get it – 463035818_is_not_a_number Jun 08 '20 at 15:33
  • @idclev463035818, true - `std::is_same<...>::value` is either true or false, _but only predicated a successful substitution_! if substitution was a failure then the whole instance would be scrubbed. – Dmitry Jun 08 '20 at 15:36
  • afaik the idomatic way is to write predicates such that they either evaluate to `false` or `true` as you have it already in your solution. All standard type traits I know are not meant to fail on substitution but rahter evaluate to `true` or `false` and then you can use that with `enable_if` to fail on substitution – 463035818_is_not_a_number Jun 08 '20 at 15:36
  • 1
    If `std::is_same{}` fails, then it's neither `std::enable_if`'s, nor `std::is_same`'s fault. You need to know exactly what your predicates are and work around them – Piotr Skotnicki Jun 08 '20 at 15:37
  • exanding on Piotr comment, his example can fail if `X` has no `type` but then you would simply use some type trait `has_type::value` that (again) does not fail, but evaluates to `true` or `false` – 463035818_is_not_a_number Jun 08 '20 at 15:39
  • i am getting the impression that it is a [xy problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – 463035818_is_not_a_number Jun 08 '20 at 15:41
  • okay, I agree, `std::is_same<>` was a bad example - that one indeed would produce a true/false, let me find an example where it would not work – Dmitry Jun 08 '20 at 15:44
  • It's common for predicate declarations to derive from `false_type` and then let specialisations derive `true_type`. Example: `template struct is_int : false_type {}; template <> struct is_int : true_type {};`. That way you are sure you will never get instantiation errors but the result in those cases will be always false. – Jorge Bellon Jun 09 '20 at 08:50

1 Answers1

2

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.

463035818_is_not_a_number
  • 64,173
  • 8
  • 58
  • 126
  • appreciate your effort for coming up the explanation and example, approving it as the answer – Dmitry Jun 09 '20 at 11:33
  • "dirty" I was calling it in a sense it was just something spontaneous and unvetted, while not being aware of the idiomatic solution – Dmitry Jun 09 '20 at 11:35
  • @Dmitry I am not fluent in SFINAE, but I am eager to understand it better, so I tried to come up with an example where it would be problematic and that is what I would use as solution. For what is "idiomatic" I am not really the right person ;). This talk was a huge revelation for me, he explains very useful stuff in a basic and simple way: https://www.youtube.com/watch?v=PFdWqa68LmA – 463035818_is_not_a_number Jun 09 '20 at 11:36