I'll do this in C++17.
First we start with the idea of override:
template<class T>
struct override_helper { using type=T; };
template<class T>
using override_helper_t = typename override_helper<T>::type;
template<class R, class...Args>
struct override_helper<R(*)(Args...)> {
struct type {
R(*f)(Args...);
R operator()(Args...args)const { return f(std::forward<Args>(args)...); }
type(R(*in)(Args...)):f(in) {}
};
};
template<class R, class...Args>
struct override_helper<R(&)(Args...)>:override_helper<R(*)(Args...)> {
using override_helper<R(*)(Args...)>::override_helper;
};
template<class...Fs>
struct override:override_helper_t<Fs>... {
using override_helper_t<Fs>::operator()...;
override(Fs...fs):override_helper_t<Fs>(std::move(fs))... {}
};
now we can do this:
auto f = override(
[](X& x) {do operations on x},
[](Z& z) {do operations on z},
[](auto&) {default operation goes here}
);
and f
is now a variant-style visitor that accepts X, Y and Z.
We then rewrite Object. Either it exposes a variant
, or we fake it.
template<class D, class Sig>
struct function_invoker;
template<class D, class R, class...Args>
struct function_invoker<D, R(Args...)> {
R operator()(Args...args)const {
return f(
static_cast<D const*>(this)->get(),
std::forward<Args>(args)...
);
}
template<class F>
function_invoker( F&& fin ):
f([](void* ptr, Args&&...args)->R{
auto* pf = static_cast<std::remove_reference_t<F>*>(ptr);
return (*pf)(std::forward<Args>(args)...);
})
{}
private:
R(*f)(void*, Args&&...) = 0;
};
template<class...Sigs>
struct function_view :
private function_invoker<function_view<Sigs...>, Sigs>...
{
template<class D, class Sig>
friend class function_invoker;
using function_invoker<function_view<Sigs...>, Sigs>::operator()...;
template<class F,
std::enable_if_t< !std::is_same<std::decay_t<F>, function_view>{}, bool> =true
>
function_view( F&& fin ):
function_invoker<function_view<Sigs...>, Sigs>( fin )...,
ptr((void*)std::addressof(fin))
{}
explicit operator bool() const { return ptr; }
private:
void* get() const { return ptr; }
void* ptr = 0;
};
this is a multiple-signature callable function pointer type erasure.
using Visitor = function_view< void(X&), void(Y&), void(Z&) >;
Visitor
is now a view type for any callable that can be invoked with any of an X, Y or Z reference.
struct Object
{
virtual void accept(Visitor visitor) = 0;
};
template<class D>
struct Object_derived:Object {
virtual void accept(Visitor visitor) final override {
visitor(*static_cast<D*>(this));
}
};
struct X:Object_derived<X> {};
struct Y:Object_derived<Y> {};
struct Z:Object_derived<Z> {};
now you can pass [](auto&){}
to Object::accept
and it compiles.
We then hook override up, and we pass in a callable with suitable overrides.
function_view
stores a pointer to the override object and a function pointer saying how to invoke each override.
Which one is picked when you implement accept
.
Live example.
Everything I have done here can be done in c++11, but is far easier in c++17, so I did it there as proof of concept.
function_view probably wants SFINAE friendly ctor that detects if its argument satisfies all of the signatures, but I got lazy.