Skip to content

Instantly share code, notes, and snippets.

@ericniebler
Last active February 3, 2024 02:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ericniebler/68d634d26e297cd8dbfbbac4aa52fab6 to your computer and use it in GitHub Desktop.
Save ericniebler/68d634d26e297cd8dbfbbac4aa52fab6 to your computer and use it in GitHub Desktop.
a utility for creating type-erasing wrappers like Folly.Poly
#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