How to use sfinae to exclude types for which a function is defined?
Asked Answered
S

2

4

Considering how the comma expression in a decltype() trailing return type can be used to check if a function can be applied:

template <class A>
auto f(A a) -> decltype(check_if_possible(a), return_type(a))

How can I negate the part before the comma to exclude cases in which check_if_possible(a) is defined?

Context: f() is an overloaded function for different A. I want to resolve an ambiguous overload between two implementations. One of them uses check_if_possible(a), the other works in cases where this cannot be applied to a.

Edit: Using std::enable_if and related things is welcome as well, but I would like to avoid additional helper functions / templates, because there are several functions similar to f() with different enable criteria. If this is not possible, then that is an answer as well ;)

Scurf answered 18/11, 2020 at 15:36 Comment(1)
since C++20, you can use requires.Impudicity
P
5

How can I negate the part before the comma to exclude cases in which check_if_possible(a) is defined?

I don't know a way, but...

Context: f is an overloaded function for different A. I want to resolve an ambiguous overload between two implementations. One of them uses check_if_possible(a), the other works in cases where this cannot be applied to a.

I usually write a f() function for both cases that demand a couple of f_helper() functions receiving an additional argument.

For example

template <typename A>
auto f (A && a)
 { return f_helper(std::forward<A>(a), 0); }

Observe the last argument: 0, it's a int

Then you can write the check_if_possible() version, receiving an additional int

template <typename A>
auto f_helper (A a, int) -> decltype(check_if_possible(a), return_type(a))
 { /* ... */ }

and a generic version, always enabled, receiving an additional long

template <typename A>
auto f_helper (A a, long)
 { /* ... */ }

This way, when A support check_if_possible(), both f_helper() are enabled but the specific version is preferred because 0 is a int, the specific version receive an additional int (exact match) and the generic an additional long (int is convertible to long but isn't an exact match).

When A doesn't support check_if_possible(), only the generic f_helper() is available.

Prosimian answered 18/11, 2020 at 16:5 Comment(2)
Thank you. I was considering priority tags as an alternative. While your answer works in the given context, I will leave the question open as it is still unanswered how (or whether) sfinae can disable functions when a certain call can be made.Scurf
@Scurf - Yes... I too am curious to know if it's possible in a simpler way.Prosimian
B
1

You could rely on C++17's constexpr if in order to provide the two different implementations:

template<class A>
auto f(A a) {
   if constexpr (supportsExpression(a)) {
      // implementation that uses the check_if_possible(a) expression
      // ...
      check_if_possible(a);
      // ...
   } else {
      // implementation that doesn't use the check_if_possible(a) expression
      // ...
   }
}

Then, provide two overloaded function templates for supportsExpression():

// uses SFINAE
template<typename T>
constexpr auto supportsExpression(T&& arg) ->
   decltype((void)check_if_possible(arg), bool{})
{
   return true;
}

// fallback
template<typename... T>
constexpr bool supportsExpression(T&&...) { return false; }

The first overload will be – due to SFINAE – discarded if the expression check_if_possible(arg) wouldn't compile.

If the first overload is not discarded from the overloaded set due to SFINAE, we end up with two suitable overloads when calling supportsExpression() in f(). In this case, however, the non-variadic function template is preferred over the variadic one, i.e., the one that returns true (the type A supports the expression).

Balfore answered 18/11, 2020 at 16:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.