As I understand it, the outcome of a function name usage might be one of the following:
- There are no (best) viable functions — overload resolution fails. The suboutcomes are:
- There are no candidates.
- There are some candidates, just none are viable.
- There is exactly one best viable function — overload resolution succeeds. The selected overload is then either
- OK — the overall call is well-formed.
- not OK (
= delete
d,protected
/private
or, perhaps, something else) — the overall call is ill-formed.
- There are more than one best viable functions — overload resolution fails with ambiguity.
The question is: How to reliably tell apart outcome #2.2 (at least some of its cases) from outcomes #1.2 and #3 (at least one of them) in the case of implicit usage of operator ()
(i.e. c(a...)
) by means of a type trait that accepts the types of the arguments (including c
) to be used in the call?
(I'm not interested in outcomes #1.1 and #2.1 as I know that #1.1 does not hold in my particular use case and #2.1 is easily detectable through SFINAE.)
A specific example. How to implement a type trait that looks something like the following
/// Would `c(a...)` result in exactly one best viable candidate?
/// (Where `decltype(c)`, `decltype(a)...` are `C`, `A...`, respectively.)
template<class C, typename... A>
inline constexpr bool has_exactly_one_best_viable_call_candidate;
so the following asserts hold?
struct WithNoViable {
void operator ()(void *);
};
struct WithDeleted {
void operator ()(long) = delete;
};
struct WithAmbiguity {
void operator ()(long);
void operator ()(long long);
};
static_assert(!has_exactly_one_best_viable_call_candidate<WithNoViable, int>);
static_assert( has_exactly_one_best_viable_call_candidate<WithDeleted, int>);
static_assert(!has_exactly_one_best_viable_call_candidate<WithAmbiguity, int>);
Note that in general nothing is known about the types of parameters nor arguments.
static_assert
is needed just to test the type trait. – Langillestd::is_invocable
, e.g.static_assert(!std::is_invocable_v<WithNoViable, int>);
, but that will not work for the deleted version... way do you want to have the ill-formed succeed? – Kirstiekirstinoperator ()(long)
overload through multiple inheritance and then check if the resulting type is callable withlong
: in case ofWithNoViable
the call would be OK, but with the other two it would be ambiguous. Perhaps, a similar type-knowledge-independent technique exists to solve my original problem. – Langilleis_deleted
trait). – HawksWrapper::operator ()
to be= delete
d. – Langilleif constexpr
context) and dispatch to the first one that works. – Hawks