In C++17, it is trivial to implement an overload(fs...)
function that, given any number of arguments fs...
satisfying FunctionObject, returns a new
In the general case, I don't think such a thing is possible even in C++17. Consider the most obnoxious case:
struct A {
template int operator()(T );
} a;
struct B {
template int operator()(T* );
} b;
ref_overload(a, b)(new int);
How could you possibly make that work? We could check that both types are callable with int*
, but both operator()
s are templates so we can't pick out their signatures. Even if we could, the deduced parameters themselves are identical - both functions take an int*
. How would you know to call b
?
In order to get this case correct, what you'd basically need to do is to inject return type into the call operators. If we could create types:
struct A' {
template index_<0> operator()(T );
};
struct B' {
template index_<1> operator()(T* );
};
Then we could use decltype(overload(declval(), declval()))::value
to pick which reference to call ourselves.
In the simplest case - when both A
and B
(and C
and ...) have one single operator()
that is not a template, this is doable - since we can actually inspect &X::operator()
and manipulate those signatures to produce the new ones that we need. This allows us to still use the compiler to do overload resolution for us.
We can also check what type overload(declval(), declval(), ...)(args...)
yields. If the best match's return type is unique almost all the viable candidates, we can still pick the correct overload in ref_overload
. This will cover more ground for us, as we can now correctly handle some cases with overloaded or templated call operators, but we will incorrectly reject many calls as ambiguous that aren't.
But in order to solve the general problem, with types that have overloaded or templated call operators with the same return type, we need something more. We need some future language features.
Full reflection would allow us to inject a return type as described above. I don't know what that would look like specifically, but I look forward to seeing Yakk's implementation of it.
An alternative potential future solution would be to use overloaded operator .. Section 4.12 includes an example that indicates that the design allows for overloading of different member functions by name through different operator.()
s. If that proposal passes in some similar form today, then implementing reference-overloading would follow the same pattern as object-overloading today, just substituting different operator .()
s for today's different operator ()
s:
template
struct ref_overload_one {
T& operator.() { return r; }
T& r;
};
template
struct ref_overloader : ref_overload_one...
{
ref_overloader(Ts&... ts)
: ref_overload_one{ts}...
{ }
using ref_overload_one::operator....; // intriguing syntax?
};