This is a prime usecase for binary variant visitation.
This shifts runtime polymorphism somewhat to static polymorphism (though a type-discriminant is still being used inside boost::variant
, but this does not use or require RTTI).
Note also, how you don't absolutely need to add separate cases for all combinations: I've demonstrated using template behaviour implementations for the case where
- arguments are of the same actual type ("identical")
- no other overload existed
I have also shown how you can still mix "classical" runtime-polymorphism by showing the overload that takes Base, A
(accepting A,B,C,... in combination with a second argument of type A (or derived).
Finally, note that this approach allows you to overload on rvalue-ness, const-ness, volatility as well.
#include <iostream>
#include <boost/variant.hpp>
#include <string>
struct Base {};
struct A: Base {};
struct B: Base {};
struct C: Base {};
typedef boost::variant<A, B, C> MyType;
struct MyBehaviour : boost::static_visitor<std::string>
{
template <typename T>
std::string operator()(T const&, T const&) const { return "identical"; }
std::string operator()(A const&, B const&) const { return "A, B"; }
std::string operator()(A const&, C const&) const { return "A, C"; }
std::string operator()(Base const&, A const&) const { return "[ABC], A"; }
std::string operator()(Base const&, Base const&) const { return "Other"; }
};
int main()
{
MyBehaviour f;
MyType a = A{},
b = B{},
c = C{};
std::cout << boost::apply_visitor(f, a, b) << "\n";
std::cout << boost::apply_visitor(f, a, c) << "\n";
std::cout << boost::apply_visitor(f, a, a) << "\n";
std::cout << boost::apply_visitor(f, b, b) << "\n";
std::cout << boost::apply_visitor(f, c, c) << "\n";
std::cout << boost::apply_visitor(f, c, a) << "\n";
std::cout << boost::apply_visitor(f, c, b) << "\n";
}
See it Live on Coliru, output:
A, B
A, C
identical
identical
identical
[ABC], A
Other