Consider the following code, where a functor derived
, inherits from two base classes base1
and base2
each providing different overloads:
// Preamble
#include <iostream>
#include <functional>
#include <type_traits>
// Base 1
struct base1 {
void operator()(int) const {
std::cout << "void base1::operator()(int) const\n";
}
void operator()(double) const {
std::cout << "void base1::operator()(double) const\n";
}
template <class Arg, class... Args>
void operator()(const Arg&, Args&&...) const {
std::cout << "void base1::operator()(const Arg&, Args&&...) const\n";
}
};
// Base 2
struct base2 {
void operator()(int) {
std::cout << "void base2::operator()(int)\n";
}
void operator()(double) {
std::cout << "void base2::operator()(double)\n";
}
};
// Derived
struct derived: base1, base2 {
using base1::operator();
using base2::operator();
void operator()(char) {
std::cout << "void derived::operator()(char)\n";
}
};
// Call
template <class F, class... Args>
void call(F&& f, Args&&... args) {
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
// Main
int main(int argc, char* argv[]) {
const derived d1;
derived d2;
derived d3;
call(d1, 1); // void base1::operator()(int) const
call(d2, 1); // void base2::operator()(int)
call(d1, 1, 2); // void base1::operator()(const Arg&, Args&&...) const
call(d2, 1, 2); // void base1::operator()(const Arg&, Args&&...) const
call(d3, 'a'); // void derived::operator()(char)
return 0;
}
The resulting output is:
void base1::operator()(int) const
void base2::operator()(int)
void base1::operator()(const Arg&, Args&&...) const
void base1::operator()(const Arg&, Args&&...) const
void derived::operator()(char)
which illustrates that depending on the arguments, the selected overload can come from base1
, base2
or derived
.
My question is: would it be possible to do the same thing at compile-time by creating a type trait which would detect which class has provided the selected overload?
This would have the form:
template <
class Base1, // Possibly cv-ref qualified base1
class Base2, // Possibly cv-ref qualified base2
class Derived, // Possibly cv-ref qualified derived
class... Args // Possibly cv-ref qualified args
>
struct overload_origin {
using base1 = std::decay_t<Base1>;
using base2 = std::decay_t<Base2>;
using derived = std::decay_t<Derived>;
using type = /* base1, base2, or derived * /
};
and when in use in the call
function in the preceding example code, would have overload_origin::type
refering to base1
, base2
, base1
, base1
, derived
for the five calls illustrated in the example code.
How to achieve such a thing with template metaprogramming?
You can derive a class from derived
and base1
. This way all calls to operator()
coming from base1
will be ambiguous:
struct derived_check: base1, derived {
using base1::operator();
using base2::operator();
};
// Main
int main(int argc, char* argv[]) {
const derived_check d1;
derived_check d2;
derived_check d3;
call(d1, 1); // error:ambiguous
call(d2, 1); // OK
call(d1, 1, 2); // error:ambiguous
call(d2, 1, 2); // error:ambiguous
return 0;
}
Then you can use the basic detection tricks to create your detection type trait.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With