/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__cpp_template_auto) || \ defined(__cpp_nontype_template_parameter_auto) #define FOLLY_POLY_NTTP_AUTO 1 #else #define FOLLY_POLY_NTTP_AUTO 0 #endif namespace folly { /// \cond namespace detail { template struct PolyRoot; using RRef_ = MetaQuoteTrait; using LRef_ = MetaQuoteTrait; template struct XRef_ : Type> {}; template using XRef = _t>; template struct XRef_ : Type>> {}; template struct XRef_ : Type>> {}; template struct XRef_ : Type> {}; template using AddCvrefOf = MetaApply, A>; } // namespace detail /// \endcond template struct Poly; template detail::AddCvrefOf& poly_cast(detail::PolyRoot&); template detail::AddCvrefOf const& poly_cast(detail::PolyRoot const&); #if !FOLLY_POLY_NTTP_AUTO #define FOLLY_AUTO class template using PolyMembers = detail::TypeList; #else #define FOLLY_AUTO auto template struct PolyMembers; #endif /// \cond namespace detail { /* ***************************************************************************** * IMPLEMENTATION NOTES * Building the Interface ---------------------- Here is a high-level description of how Poly works. Users write an interface such as: struct Mine { template struct Interface { int Exec() const { return folly::poly_call<0>(*this); } } template using Members = folly::PolyMembers<&T::Exec>; }; Then they instantiate Poly, which will have an Exec member function of the correct signature. The Exec member function comes from Mine::Interface>>, from which Poly inherits. Here's what each piece means: - PolyRoot: stores Data, which is a union of a void* (used when the Poly is storing an object on the heap or a reference) and some aligned storage (used when the Poly is storing an object in-situ). PolyRoot also stores a vtable pointer for interface I, which is a pointer to a struct containing function pointers. The function pointers are bound member functions (e.g., SomeType::Exec). More on the vtable pointer and how it is generated below. - PolyNode: provides the hooks used by folly::poly_call to dispatch to the correctly bound member function for this interface. In the context of an interface I, folly::poly_call(*this, args...) will: 1. Fetch the vtable pointer from PolyRoot, 2. Select the I portion of that vtable (which, in the case of interface extension, may only be a part of the total vtable), 3. Fetch the K-th function pointer from that vtable part, 4. Call through that function pointer, passing Data (from PolyRoot) and any additional arguments in the folly::poly_call invocation. In the case of interface extension -- for instance, if interface Mine extended interface Yours by inheriting from PolyExtends -- then interface Mine will have a list of base interfaces in a typelist called "Subsumptions". Poly will fold all the subsumed interfaces together, linearly inheriting from them. To take the example of an interface Mine that extends Yours, Poly would inherit from this type: Mine::Interface< PolyNode>>>> Through linear inheritance, Poly ends up with the public member functions of both interfaces, Mine and Yours. VTables ------- As mentioned above, PolyRoot stores a vtable pointer for interface I. The vtable is a struct whose members are function pointers. How are the types of those function pointers computed from the interface? A dummy type is created, Archetype, in much the same way that Poly's base type is computed. Instead of PolyNode and PolyRoot, there is ArchetypeNode and ArchetypeRoot. These types also provide hooks for folly::poly_call, but they are dummy hooks that do nothing. (Actually, they call std::terminate; they will never be called.) Once Archetype has been constructed, it is a concrete type that has all the member functions of the interface and its subsumed interfaces. That type is passed to Mine::Members, which takes the address of Archetype::Exec and inspects the resulting member function type. This is done for each member in the interface. From a list of [member] function pointers, it is a simple matter of metaprogramming to build a struct of function pointers. std::tuple is used for this. An extra field is added to the tuple for a function that handles all of the "special" operations: destruction, copying, moving, getting the type information, getting the address of the stored object, and fetching a fixed-up vtable pointer for reference conversions (e.g., I -> I&, I& -> I const&, etc). Subsumed interfaces are handled by having VTable inherit from BasePtr, where BasePtr has only one member of type VTable const*. Now that the type of VTable is computed, how are the fields populated? Poly default-constructs to an empty state. Its vtable pointer points to a vtable whose fields are initialized with the addresses of functions that do nothing but throw a BadPolyAccess exception. That way, if you call a member function on an empty Poly, you get an exception. The function pointer corresponding to the "special" operations points to a no-op function; copying, moving and destroying an empty Poly does nothing. On the other hand, when you pass an object of type T satisfying interface I to Poly's constructor or assignment operator, a vtable for {I,T} is reified by passing type T to I::Members, thereby creating a list of bindings for T's member functions. The address of this vtable gets stored in the PolyRoot subobject, imbuing the Poly object with the behaviors of type T. The T object itself gets stored either on the heap or in the aligned storage within the Poly object itself, depending on the size of T and whether or not it has a noexcept move constructor. */ template class U> struct IsInstanceOf : std::false_type {}; template class U> struct IsInstanceOf, U> : std::true_type {}; template decltype(auto) if_constexpr(std::true_type, Then then) { return then(Identity{}); } template void if_constexpr(std::false_type, Then) {} template decltype(auto) if_constexpr(std::true_type, Then then, Else) { return then(Identity{}); } template decltype(auto) if_constexpr(std::false_type, Then, Else else_) { return else_(Identity{}); } enum class Op : short { eNuke, eMove, eCopy, eType, eAddr, eRefr }; enum class RefType : std::uintptr_t { eRvalue, eLvalue, eConstLvalue }; struct Data; template struct PolyVal; template struct PolyRef; struct PolyAccess; template using IsPoly = IsInstanceOf, Poly>; // Given an interface I and a concrete type T that satisfies the interface // I, create a list of member function bindings from members of T to members // of I. template using MembersOf = typename I::template Members>; // Given an interface I and a base type T, create a type that implements // the interface I in terms of the capabilities of T. template using InterfaceOf = typename I::template Interface; #if !FOLLY_POLY_NTTP_AUTO template using Member = std::integral_constant; template using MemberType = typename M::value_type; template inline constexpr MemberType memberValue() noexcept { return M::value; } template struct MakeMembers { template using Members = PolyMembers...>; }; template MakeMembers deduceMembers(Ts...); template > struct MemberDef; template struct MemberDef { static R value(D& d, As... as) { return folly::invoke(memberValue(), d, static_cast(as)...); } }; template struct MemberDef { static R value(D const& d, As... as) { return folly::invoke(memberValue(), d, static_cast(as)...); } }; #else template using MemberType = decltype(M); template inline constexpr MemberType memberValue() noexcept { return M; } #endif struct PolyBase {}; template struct SubsumptionsOf_ { using type = TypeList<>; }; template using InclusiveSubsumptionsOf = TypePushFront<_t>, I>; template struct SubsumptionsOf_> { using type = TypeJoin>>; }; template using SubsumptionsOf = TypeReverseUnique<_t>>; struct Bottom { template [[noreturn]] /* implicit */ operator T &&() const { std::terminate(); } }; using ArchetypeNode = MetaQuote; template struct ArchetypeRoot; template using Archetype = TypeFold, ArchetypeRoot, ArchetypeNode>; struct ArchetypeBase : Bottom { ArchetypeBase() = default; template /* implicit */ ArchetypeBase(T&&); template [[noreturn]] Bottom _polyCall_(As&&...) const { std::terminate(); } friend bool operator==(ArchetypeBase const&, ArchetypeBase const&); friend bool operator!=(ArchetypeBase const&, ArchetypeBase const&); friend bool operator<(ArchetypeBase const&, ArchetypeBase const&); friend bool operator<=(ArchetypeBase const&, ArchetypeBase const&); friend bool operator>(ArchetypeBase const&, ArchetypeBase const&); friend bool operator>=(ArchetypeBase const&, ArchetypeBase const&); friend Bottom operator++(ArchetypeBase const&); friend Bottom operator++(ArchetypeBase const&, int); friend Bottom operator--(ArchetypeBase const&); friend Bottom operator--(ArchetypeBase const&, int); friend Bottom operator+(ArchetypeBase const&, ArchetypeBase const&); friend Bottom operator+=(ArchetypeBase const&, ArchetypeBase const&); friend Bottom operator-(ArchetypeBase const&, ArchetypeBase const&); friend Bottom operator-=(ArchetypeBase const&, ArchetypeBase const&); friend Bottom operator*(ArchetypeBase const&, ArchetypeBase const&); friend Bottom operator*=(ArchetypeBase const&, ArchetypeBase const&); friend Bottom operator/(ArchetypeBase const&, ArchetypeBase const&); friend Bottom operator/=(ArchetypeBase const&, ArchetypeBase const&); friend Bottom operator%(ArchetypeBase const&, ArchetypeBase const&); friend Bottom operator%=(ArchetypeBase const&, ArchetypeBase const&); friend Bottom operator<<(ArchetypeBase const&, ArchetypeBase const&); friend Bottom operator<<=(ArchetypeBase const&, ArchetypeBase const&); friend Bottom operator>>(ArchetypeBase const&, ArchetypeBase const&); friend Bottom operator>>=(ArchetypeBase const&, ArchetypeBase const&); }; template struct ArchetypeRoot : ArchetypeBase { template using _polySelf_ = Archetype, Node>>; using _polyInterface_ = I; }; struct Data { Data() = default; // Suppress compiler-generated copy ops to not copy anything: Data(Data const&) {} Data& operator=(Data const&) { return *this; } union { void* pobj_ = nullptr; std::aligned_storage_t buff_; }; }; template using Arg = If, Archetype>::value, Poly>, U>; template using Ret = If, Archetype>::value, AddCvrefOf, U>, U>; template struct SignatureOf_; template struct SignatureOf_ { using type = Ret (*)(Data&, Arg...); }; template struct SignatureOf_ { using type = Ret (*)(Data const&, Arg...); }; #if FOLLY_HAVE_NOEXCEPT_FUNCTION_TYPE template struct SignatureOf_ { using type = std::add_pointer_t(Data&, Arg...) noexcept>; }; template struct SignatureOf_ { using type = std::add_pointer_t(Data const&, Arg...) noexcept>; }; #endif template struct SignatureOf_ { using type = Ret (*)(Data&, Arg...); }; template struct SignatureOf_ { using type = Ret (*)(Data const&, Arg...); }; template using SignatureOf = _t, I>>; template > struct ArgTypes_; template struct ArgTypes_ { using type = TypeList; }; #if FOLLY_HAVE_NOEXCEPT_FUNCTION_TYPE template struct ArgTypes_ { using type = TypeList; }; #endif template using ArgTypes = _t>; template using FnPtr = R (*)(Args...); struct ThrowThunk { template constexpr /* implicit */ operator FnPtr() const noexcept { struct _ { static R call(Args...) { throw_exception(); } }; return &_::call; } }; inline constexpr ThrowThunk throw_() noexcept { return ThrowThunk{}; } template inline constexpr bool inSitu() noexcept { return !std::is_reference::value && sizeof(std::decay_t) <= sizeof(Data) && std::is_nothrow_move_constructible>::value; } template T& get(Data& d) noexcept { if (inSitu()) { return *(std::add_pointer_t)static_cast(&d.buff_); } else { return *static_cast>(d.pobj_); } } template T const& get(Data const& d) noexcept { if (inSitu()) { return *(std::add_pointer_t)static_cast(&d.buff_); } else { return *static_cast>(d.pobj_); } } enum class State : short { eEmpty, eInSitu, eOnHeap }; template struct IsPolyRef : std::false_type {}; template struct IsPolyRef> : std::true_type {}; template decltype(auto) convert(U&& u) { return detail::if_constexpr( StrictConjunction< IsPolyRef>, Negation>>(), [&](auto id) -> decltype(auto) { return poly_cast>(id(u).get()); }, [&](auto id) -> U&& { return static_cast(id(u)); }); } template struct IsConstMember : std::false_type {}; template struct IsConstMember : std::true_type {}; template struct IsConstMember : std::true_type {}; #if FOLLY_HAVE_NOEXCEPT_FUNCTION_TYPE template struct IsConstMember : std::true_type {}; template struct IsConstMember : std::true_type {}; #endif template < class T, FOLLY_AUTO User, class I, class = ArgTypes, class = Bool> struct ThunkFn { template constexpr /* implicit */ operator FnPtr() const noexcept { return nullptr; } }; template struct ThunkFn< T, User, I, TypeList, Bool< !std::is_const>::value || IsConstMember>::value>> { template constexpr /* implicit */ operator FnPtr() const noexcept { struct _ { static R call(D& d, As... as) { return folly::invoke( memberValue(), get(d), convert(static_cast(as))...); } }; return &_::call; } }; template < class I, class = MembersOf>, class = SubsumptionsOf> struct VTable; template inline constexpr ThunkFn thunk() noexcept { return ThunkFn{}; } template constexpr VTable const* vtable() noexcept { return &StaticConst>::value; } template struct VTableFor : VTable { constexpr VTableFor() noexcept : VTable{Type{}} {} }; template constexpr VTable const* vtableFor() noexcept { return &StaticConst>::value; } template constexpr void* vtableForRef(RefType ref) { switch (ref) { case RefType::eRvalue: return const_cast*>(vtableFor()); case RefType::eLvalue: return const_cast*>(vtableFor()); case RefType::eConstLvalue: return const_cast*>(vtableFor()); } return nullptr; } template < class I, class T, std::enable_if_t::value, int> = 0> void* execOnHeap(Op op, Data* from, void* to) { switch (op) { case Op::eNuke: break; case Op::eMove: case Op::eCopy: static_cast(to)->pobj_ = from->pobj_; break; case Op::eType: return const_cast(static_cast(&typeid(T))); case Op::eAddr: if (*static_cast(to) == typeid(T)) { return from->pobj_; } throw_exception(); case Op::eRefr: return vtableForRef>( static_cast(reinterpret_cast(to))); } return nullptr; } template < class I, class T, std::enable_if_t>::value, int> = 0> void* execOnHeap(Op op, Data* from, void* to) { switch (op) { case Op::eNuke: delete &get(*from); break; case Op::eMove: static_cast(to)->pobj_ = std::exchange(from->pobj_, nullptr); break; case Op::eCopy: detail::if_constexpr(std::is_copy_constructible(), [&](auto id) { static_cast(to)->pobj_ = new T(id(get(*from))); }); break; case Op::eType: return const_cast(static_cast(&typeid(T))); case Op::eAddr: if (*static_cast(to) == typeid(T)) { return from->pobj_; } throw_exception(); case Op::eRefr: return vtableForRef>( static_cast(reinterpret_cast(to))); } return nullptr; } template void* execInSitu(Op op, Data* from, void* to) { switch (op) { case Op::eNuke: get(*from).~T(); break; case Op::eMove: ::new (static_cast(&static_cast(to)->buff_)) T(std::move(get(*from))); get(*from).~T(); break; case Op::eCopy: detail::if_constexpr(std::is_copy_constructible(), [&](auto id) { ::new (static_cast(&static_cast(to)->buff_)) T(id(get(*from))); }); break; case Op::eType: return const_cast(static_cast(&typeid(T))); case Op::eAddr: if (*static_cast(to) == typeid(T)) { return &from->buff_; } throw_exception(); case Op::eRefr: return vtableForRef>( static_cast(reinterpret_cast(to))); } return nullptr; } inline void* noopExec(Op op, Data*, void*) { if (op == Op::eAddr) throw_exception(); return const_cast(static_cast(&typeid(void))); } template struct BasePtr { VTable const* vptr_; }; template constexpr void* (*getOpsImpl(std::true_type) noexcept)(Op, Data*, void*) { return &execInSitu; } template constexpr void* (*getOpsImpl(std::false_type) noexcept)(Op, Data*, void*) { return &execOnHeap; } template constexpr void* (*getOps() noexcept)(Op, Data*, void*) { return getOpsImpl(std::integral_constant()>{}); } template struct VTable, TypeList> : BasePtr..., std::tuple...> { private: template constexpr VTable(Type, PolyMembers) noexcept : BasePtr{vtableFor()}..., std::tuple...>{thunk()...}, state_{inSitu() ? State::eInSitu : State::eOnHeap}, ops_{getOps()} {} public: constexpr VTable() noexcept : BasePtr{vtable()}..., std::tuple...>{ static_cast>(throw_())...}, state_{State::eEmpty}, ops_{&noopExec} {} template explicit constexpr VTable(Type) noexcept : VTable{Type{}, MembersOf{}} {} State state_; void* (*ops_)(Op, Data*, void*); }; template constexpr VTable const& select(VTable<_t>> const& vtbl) noexcept { return vtbl; } template constexpr VTable const& select(BasePtr<_t>> const& base) noexcept { return *base.vptr_; } struct PolyAccess { template static auto call(This&& _this, As&&... args) -> decltype(static_cast(_this).template _polyCall_( static_cast(args)...)) { static_assert( !IsInstanceOf, Poly>::value, "When passing a Poly<> object to call(), you must explicitly " "say which Interface to dispatch to, as in " "call<0, MyInterface>(self, args...)"); return static_cast(_this).template _polyCall_( static_cast(args)...); } template using Iface = typename remove_cvref_t::_polyInterface_; template static typename remove_cvref_t::template _polySelf_ self_(); template > static decltype(auto) cast(Poly&& _this) { using Ret = AddCvrefOf, Poly&&>; return static_cast( *static_cast>(_this.vptr_->ops_( Op::eAddr, const_cast(static_cast(&_this)), const_cast(static_cast(&typeid(T)))))); } template static decltype(auto) root(Poly&& _this) noexcept { return static_cast(_this)._polyRoot_(); } template static std::type_info const& type(PolyRoot const& _this) noexcept { return *static_cast( _this.vptr_->ops_(Op::eType, nullptr, nullptr)); } template static VTable> const* vtable( PolyRoot const& _this) noexcept { return _this.vptr_; } template static Data* data(PolyRoot& _this) noexcept { return &_this; } template static Data const* data(PolyRoot const& _this) noexcept { return &_this; } template static Poly move(PolyRoot const& _this) noexcept { return Poly{_this, Type{}}; } template static Poly move(PolyRoot const& _this) noexcept { return Poly{_this, Type{}}; } }; template struct PolyNode : Tail { private: friend PolyAccess; using Tail::Tail; template decltype(auto) _polyCall_(As&&... as) { return std::get(select(*PolyAccess::vtable(*this)))( *PolyAccess::data(*this), static_cast(as)...); } template decltype(auto) _polyCall_(As&&... as) const { return std::get(select(*PolyAccess::vtable(*this)))( *PolyAccess::data(*this), static_cast(as)...); } }; struct MakePolyNode { template using apply = InterfaceOf>; }; template struct PolyRoot : private PolyBase, private Data { friend PolyAccess; friend Poly; friend PolyVal; friend PolyRef; template using _polySelf_ = Poly, Node>>; using _polyInterface_ = I; private: PolyRoot& _polyRoot_() noexcept { return *this; } PolyRoot const& _polyRoot_() const noexcept { return *this; } VTable> const* vptr_ = vtable>(); }; template using PolyImpl = TypeFold< InclusiveSubsumptionsOf>, PolyRoot, MakePolyNode>; // A const-qualified function type means the user is trying to disambiguate // a member function pointer. template // Fun = R(As...) const struct Sig { template constexpr Fun T::*operator()(Fun T::*t) const /* nolint */ volatile noexcept { return t; } template constexpr F T::*operator()(F T::*t) const /* nolint */ volatile noexcept { return t; } }; // A functon type with no arguments means the user is trying to disambiguate // a member function pointer. template struct Sig : Sig { using Fun = R(); using Sig::operator(); template constexpr Fun T::*operator()(Fun T::*t) const noexcept { return t; } }; template struct SigImpl : Sig { using Fun = R(As...); using Sig::operator(); template constexpr Fun T::*operator()(Fun T::*t) const noexcept { return t; } constexpr Fun* operator()(Fun* t) const noexcept { return t; } template constexpr F* operator()(F* t) const noexcept { return t; } }; // The user could be trying to disambiguate either a member or a free function. template struct Sig : SigImpl {}; // This case is like the one above, except we want to add an overload that // handles the case of a free function where the first argument is more // const-qualified than the user explicitly specified. template struct Sig : SigImpl { using CCFun = R(A const&, As...); using SigImpl::operator(); constexpr CCFun* operator()(CCFun* t) const /* nolint */ volatile noexcept { return t; } }; template struct ModelsInterface2_ : std::false_type {}; template struct ModelsInterface2_< T, I, void_t< std::enable_if_t< std::is_constructible, I>, T>::value>, MembersOf, std::decay_t>>> : std::true_type {}; template struct ModelsInterface_ : std::false_type {}; template struct ModelsInterface_< T, I, std::enable_if_t< Negation>>::value>> : ModelsInterface2_ {}; template struct ModelsInterface : ModelsInterface_ {}; template struct ValueCompatible : std::is_base_of {}; // This prevents PolyRef's converting constructors and assignment operators // from being considered as copy constructors and assignment operators: template struct ValueCompatible : std::false_type {}; template struct ReferenceCompatible : std::is_constructible {}; // This prevents PolyRef's converting constructors and assignment operators // from being considered as copy constructors and assignment operators: template struct ReferenceCompatible : std::false_type {}; } // namespace detail /// \endcond } // namespace folly #undef FOLLY_AUTO