Last active
February 3, 2024 02:18
-
-
Save ericniebler/68d634d26e297cd8dbfbbac4aa52fab6 to your computer and use it in GitHub Desktop.
a utility for creating type-erasing wrappers like Folly.Poly
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cstdio> | |
#include <memory> | |
#include <stdexcept> | |
#include <stdexec/__detail/__meta.hpp> | |
#include <typeinfo> | |
#include <iostream> | |
#pragma GCC diagnostic push | |
#pragma GCC diagnostic ignored "-Wpragmas" | |
#pragma GCC diagnostic ignored "-Wunknown-warning-option" | |
#pragma GCC diagnostic ignored "-Wexceptions" | |
#pragma GCC diagnostic ignored "-Wterminate" | |
using namespace stdexec; | |
template <class Interface> struct any_unique; | |
template <class Interface> struct interface_for; | |
template <class Interface, class T> struct model_for; | |
template <class T, class Interface> | |
concept model_of = !std::same_as<T, any_unique<Interface>> && | |
requires { typename Interface::template vtable_for<T>; }; | |
template <class Interface, class Fun, bool Nothrow = false> struct vfun; | |
template <class Interface, class R, class... Args, bool Nothrow> | |
struct vfun<Interface, R(Args...), Nothrow> { | |
template <class C, auto Mbr> | |
static R impl(interface_for<Interface> *self, | |
Args... args) noexcept(Nothrow) { | |
if constexpr (requires { *Mbr; }) { | |
return (*Mbr)(static_cast<model_for<Interface, C> *>(self)->value, | |
(Args &&)args...); | |
} else { | |
return (static_cast<model_for<Interface, C> *>(self)->value.* | |
Mbr)((Args &&)args...); | |
} | |
} | |
R (*fn)(interface_for<Interface> *, Args...) noexcept(Nothrow); | |
}; | |
template <class Interface, class R, class... Args, bool Nothrow> | |
struct vfun<Interface, R(Args...) const, Nothrow> { | |
template <class C, auto Mbr> | |
static R impl(const interface_for<Interface> *self, | |
Args... args) noexcept(Nothrow) { | |
return (static_cast<const model_for<Interface, C> *>(self)->value.* | |
Mbr)((Args &&)args...); | |
} | |
R (*fn)(const interface_for<Interface> *, Args...) noexcept(Nothrow); | |
}; | |
template <class Interface, class R, class... Args> | |
struct vfun<Interface, R(Args...) noexcept> | |
: vfun<Interface, R(Args...), true> {}; | |
template <class Interface, class R, class... Args> | |
struct vfun<Interface, R(Args...) const noexcept> | |
: vfun<Interface, R(Args...) const, true> {}; | |
template <class Interface, class T, auto Mbr> constexpr int vfun_v = 0; | |
template <class Interface, class T, class Fun, class C, Fun C::*Mbr> | |
constexpr vfun<Interface, Fun> vfun_v<Interface, T, Mbr> = { | |
&vfun<Interface, Fun>::template impl<T, Mbr>}; | |
template <class Interface, class T, class Ret, class Arg, class... Args, | |
Ret (*Pfn)(Arg, Args...)> | |
constexpr vfun<Interface, Ret(Args...)> vfun_v<Interface, T, Pfn> = { | |
&vfun<Interface, Ret(Args...)>::template impl<T, Pfn>}; | |
template <class Interface, class T, class Ret, class Arg, class... Args, | |
Ret (*Pfn)(Arg, Args...) noexcept> | |
constexpr vfun<Interface, Ret(Args...), true> vfun_v<Interface, T, Pfn> = { | |
&vfun<Interface, Ret(Args...), true>::template impl<T, Pfn>}; | |
template <class = void> // to ensure all types are unique for the sake of EBO | |
struct inherit { | |
template <class... Bases> struct bases : Bases... { | |
template <class T> static constexpr bases vtables_for() { | |
return bases{Bases::interface::template vtable_for<T>::value...}; | |
} | |
}; | |
template <class... Bases> using __f = bases<Bases...>; | |
}; | |
template <class... Interfaces> struct extends {}; | |
template <class Interface> using _bases = typename Interface::extends; | |
template <class Interface> | |
using bases_of = __minvoke<__with_default_q<_bases, extends<>>, Interface>; | |
template <class Interface> using vtable_for = interface_for<Interface>::vtable; | |
template <class Interface> | |
using base_vtables = __mapply<__transform<__q<vtable_for>, inherit<Interface>>, | |
bases_of<Interface>>; | |
template <class Interface> struct rtti { | |
const std::type_info &value_type_; | |
const std::type_info &interface_type_; | |
const std::type_info &value_typeid() const noexcept { return value_type_; } | |
const std::type_info &interface_typeid() const noexcept { | |
return interface_type_; | |
} | |
}; | |
template <class Interface, class... VFuns> | |
struct vtable : rtti<Interface>, base_vtables<Interface>, VFuns... { | |
using interface = Interface; | |
using rtti<Interface>::value_typeid; | |
using rtti<Interface>::interface_typeid; | |
template <std::size_t Index> auto nth() const { | |
return static_cast<const __m_at_c<Index, VFuns...> *>(this)->fn; | |
} | |
}; | |
template <class Interface, class T, auto... Mbrs> struct make_vtable_for { | |
static void _dtor(const interface_for<Interface> *self) noexcept { | |
static_cast<const model_for<Interface, T> *>(self)->value.~T(); | |
} | |
using type = vtable<Interface, vfun<Interface, void() const noexcept>, // dtor | |
decltype(auto(vfun_v<Interface, T, Mbrs>))...>; | |
static constexpr type value = { | |
{typeid(T), typeid(Interface)}, | |
base_vtables<Interface>::template vtables_for<T>(), | |
{&_dtor}, | |
vfun_v<Interface, T, Mbrs>...}; | |
}; | |
struct abstract { | |
template <std::size_t Index, class Self, class... Args> | |
[[noreturn]] void dispatch(this Self &&, Args &&...) noexcept { | |
throw std::runtime_error("pure virtual function called"); | |
} | |
}; | |
template <class Interface> struct interface_for { | |
using vtable = Interface::template vtable_for< | |
typename Interface::template interface<abstract>>::type; | |
~interface_for() { vptr->template nth<0>()(this); } | |
const vtable *vptr; | |
}; | |
template <class Interface, class T> | |
struct model_for final : interface_for<Interface> { | |
~model_for() = delete; | |
T value; | |
}; | |
template <class Interface> struct pimpl_for { | |
template <__none_of<pimpl_for> T> | |
requires(!std::derived_from<T, pimpl_for>) && model_of<T, Interface> | |
pimpl_for(T t) | |
: pimpl_(new model_for<Interface, T>{ | |
{&Interface::template vtable_for<T>::value}, std::move(t)}) {} | |
template <class OtherInterface = Interface> | |
const vtable_for<OtherInterface> *get_vptr() const noexcept { | |
return pimpl_.get()->vptr; | |
} | |
template <class OtherInterface = Interface> | |
interface_for<OtherInterface> *get_ptr() const noexcept { | |
void *pimpl = pimpl_.get(); | |
return static_cast<interface_for<OtherInterface> *>(pimpl); | |
} | |
template <std::size_t I, class Self, class... Args> | |
decltype(auto) dispatch(this Self &self, Args &&...args) { | |
// +1 here because the destructor is in position 0 | |
return self.get_vptr()->template nth<I + 1>()(self.get_ptr(), | |
(Args &&)args...); | |
} | |
std::unique_ptr<interface_for<Interface>> pimpl_; | |
}; | |
template <class Interface, class Derived> struct base_of { | |
template <std::size_t I, class Self, class... Args> | |
decltype(auto) dispatch(this Self &self, Args &&...args) { | |
auto &derived = static_cast<const any_unique<Derived> &>(self); | |
// +1 here because the destructor is in position 0 | |
return derived.template get_vptr<Interface>()->template nth<I + 1>()( | |
derived.template get_ptr<Interface>(), (Args &&)args...); | |
} | |
}; | |
template <class Interface, class Derived> | |
struct any_bases : Interface::template interface<base_of<Interface, Derived>> { | |
}; | |
template <class Interface, class Derived = Interface> | |
using make_any_bases = __mapply< | |
__transform<__mbind_back_q<any_bases, Derived>, inherit<Interface>>, | |
bases_of<Interface>>; | |
template <class Interface, class Derived> | |
requires __mvalid<_bases, Interface> | |
struct any_bases<Interface, Derived> | |
: Interface::template interface<base_of<Interface, Derived>>, | |
make_any_bases<Interface, Derived> {}; | |
template <class Interface> | |
using make_any_unique = Interface::template interface<pimpl_for<Interface>>; | |
template <class Interface> | |
struct any_unique : make_any_unique<Interface>, make_any_bases<Interface> { | |
any_unique() = delete; | |
using make_any_unique<Interface>::make_any_unique; | |
// template <__none_of<Interface> OtherInterface> | |
// requires std::convertible_to<vtable_for<OtherInterface>*, | |
// vtable_for<Interface>*> | |
// any_unique(any_unique<OtherInterface> other) noexcept { | |
// other.get_ptr() | |
// } | |
const std::type_info &type() const noexcept { | |
return this->get_vptr()->value_typeid(); | |
} | |
}; | |
#pragma GCC diagnostic pop | |
struct ibase { | |
template <class Base> struct interface : Base { | |
using Base::Base; | |
void base() { this->template dispatch<0>(); } | |
}; | |
template <class T> using vtable_for = make_vtable_for<ibase, T, &T::base>; | |
}; | |
struct ibase2 { | |
template <class Base> struct interface : Base { | |
using Base::Base; | |
void base2() { this->template dispatch<0>(); } | |
}; | |
template <class T> using vtable_for = make_vtable_for<ibase2, T, &T::base2>; | |
}; | |
struct ifoo : extends<ibase, ibase2> { | |
template <class Base> struct interface : Base { | |
using Base::Base; | |
void foo() { this->template dispatch<0>(); } | |
void bar(int i) const { this->template dispatch<1>(i); } | |
}; | |
template <class T> | |
using vtable_for = make_vtable_for<ifoo, T, &T::foo, &T::bar>; | |
}; | |
struct itest { | |
template <class Base> struct interface : Base { | |
using Base::Base; | |
friend std::ostream &operator<<(std::ostream &sout, const interface &self) { | |
self.template dispatch<0>(sout); | |
return sout; | |
} | |
}; | |
template <class T> | |
using vtable_for = | |
make_vtable_for<itest, T, +[](const T &t, std::ostream &sout) -> void { | |
sout << t; | |
}>; | |
}; | |
struct Fooable { | |
~Fooable() { std::printf("~MyFoo()\n"); } | |
void base() { std::printf("MyFoo::base(), j==%d\n", j); } | |
void base2() { std::printf("MyFoo::base2(), j==%d\n", j); } | |
void foo() { std::printf("MyFoo::foo(), j==%d\n", j); } | |
void bar(int i) const { std::printf("MyFoo::bar(int) : %d\n", i); } | |
int j = 16; | |
}; | |
int main() { | |
any_unique<ifoo> a(Fooable{}); | |
a.foo(); | |
a.bar(42); | |
a.base(); | |
a.base2(); | |
std::printf("%s\n", a.type().name()); | |
assert(a.type() == typeid(Fooable)); | |
any_unique<itest> x{42}; | |
std::cout << x << '\n'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment