template<class Base, class T>
void SafelyAssignPointer(Base*& bp, T* dp) {
if constexpr (std::is_base_of_v<Base, T>) {
bp = dp;
}
}
template<class T, class U>
void SafelyAssign(T& t, U u) {
if constexpr (requires { t = u; }) {
t = u;
}
}
SafelyAssignPointer<std::remove_const_t<Base>, Derived>(te_pointer_to_base->typed_pointer, pointer_to_derived)
SafelyAssign(te_pointer_to_base->typed_pointer, pointer_to_derived)
struct TypelessPointer {
template<class D>
void TryToSafelyAssign(D *pd);
virtual bool IsNull() const = 0;
virtual ~TypelessPointer() = default;
};
template<class T>
struct TypeErasedPointer : TypelessPointer {
T *p_ = nullptr;
bool IsNull() const override { return p_ == nullptr; }
};
template<class D>
void TypelessPointer::TryToSafelyAssign(D *pd) {
// In here, we want to know if `pd` can be converted to the-type-held-by-my-`p_`,
// but we haven't got access to my `p_` because that's in the base class.
!!!
}
struct TypelessPointer {
template<class D>
void TryToSafelyAssign(D *pd) {
AssignHelper([&]() { throw pd; });
}
virtual void AssignHelper(std::function<void()>) = 0;
virtual bool IsNull() const = 0;
virtual ~TypelessPointer() = default;
};
template<class T>
struct TypeErasedPointer : TypelessPointer {
T *p_ = nullptr;
bool IsNull() const override { return p_ == nullptr; }
void AssignHelper(std::function<void()> f) override {
try {
f();
} catch (T *p) {
p_ = p;
} catch (...) {}
}
};
/* This will demonstrate a simple reference-counting smart pointer with clone() functionality,that could be implemented using experimental std::basesPermission hereby granted to use this code for any purpose that will directly or indirectlylead to std::bases actually being a part of the C++ standard.Billy*/#include <type_traits>#include <stdexcept>#include <iostream>/****** Some polymorphic classes for testing ******/namespace TestClasses {struct A{int a = 0;virtual constexpr int get() const { return a; }virtual ~A() = default;};struct B : virtual A{int b = 1;constexpr int get() const override { return b; }};struct C : virtual A{int c = 2;constexpr int get() const override { return c; }};struct D : B, C{int d = 3;constexpr int get() const override { return d; }};struct E{int e = 4;};}/****** Simple Type Erasure for pointers ******/struct TypelessPointer{virtual constexpr bool IsNull() const = 0;virtual ~TypelessPointer() = default;};template<typename T>struct TypeErasedPointer : TypelessPointer{T* typed_pointer = nullptr;constexpr bool IsNull() const override { return typed_pointer == nullptr; }};/***** Some concepts we need ******/template<class D, class B>concept DerivedFrom = std::is_base_of<B, D>::value;template<class D, class B>concept NotDerivedFrom = !std::is_base_of<B, D>::value;template<typename T>concept Copyable = std::is_copy_constructible<T>::value;template<typename To, typename From>concept PointerConvertible = std::is_convertible<From*, To*>::value;/***** Building up the Pointer assignment functionality we need *****/template<typename Base, typename Derived> requires DerivedFrom<Derived, Base>constexpr void SafelyAssignPointer(Base*& bp, Derived* dp){// I can safely do this conversion because I know both types, Derrived and Base, at compile timebp = static_cast<Base*>(dp);}template<typename NotBase, typename NotDerived> requires NotDerivedFrom<NotDerived, NotBase>constexpr void SafelyAssignPointer(NotBase*& bp, NotDerived* dp){// this overload represents an invalid assignment, so don't do anything// (wouldn't need this with proper working version of std::bases)}template<typename Base, typename Derived>constexpr bool TryToSafelyAssignTypelessPointer(TypelessPointer* typeless_pointer_to_base, Derived* pointer_to_derived){// I am not sure if Base is the type you are looking for but I can perform a runtime check and assign it if it is.auto te_pointer_to_base = dynamic_cast<TypeErasedPointer<std::remove_const_t<Base>>*>(typeless_pointer_to_base);if (te_pointer_to_base != nullptr) SafelyAssignPointer<std::remove_const_t<Base>, Derived>(te_pointer_to_base->typed_pointer, pointer_to_derived);return te_pointer_to_base != nullptr;}template<typename ...A>constexpr void Please(A... do_things) {}template<typename Derived, typename ...Bases>constexpr void SetTypelessPointerToBase(TypelessPointer* typeless_pointer_to_base, Derived* pointer_to_derived){// If you give me a list of base classes then I can check each one at runtime to try and find the one you gave me.if (typeless_pointer_to_base == nullptr) return;Please(TryToSafelyAssignTypelessPointer<Bases, Derived>(typeless_pointer_to_base, pointer_to_derived)...);// I should check the Derived class itself, alsoTryToSafelyAssignTypelessPointer<Derived, Derived>(typeless_pointer_to_base, pointer_to_derived);if (pointer_to_derived != nullptr && typeless_pointer_to_base->IsNull()) {throw std::runtime_error("couldn't find base class");}}template<typename Derived, typename BaseList>struct BaseListUnpacker {};template<typename Derived, template <typename ...> typename TypeList, typename ...Bases>struct BaseListUnpacker<Derived, TypeList<Bases...>>{// this specialization exists solely to unpack the list of bases and pass it alongstatic constexpr void SetTyplessPointer(TypelessPointer* typeless_pointer_to_base, Derived* pointer_to_derived){SetTypelessPointerToBase<Derived, Bases...>(typeless_pointer_to_base, pointer_to_derived);}};/***** Here we start implementing the smart pointer *****/struct TypelessObjectWithRefCounter{// Because the Smart Pointer wants to work the polymophic objects,// we're using a Type erased object to manage storage of the object.// We'll store the object with the ref count to save allocationslong ref_count = 1;constexpr void AddRef() { ++ref_count; }constexpr void DecRef() { --ref_count; }constexpr bool IsExpired() const { return ref_count == 0; }virtual constexpr TypelessObjectWithRefCounter* clone() const = 0; // note that Type Erasure things can clone themselves easily in C++virtual constexpr void SetTypelessToPointToObject(TypelessPointer* teptr) = 0; // this can't be implmented without std::basesconstexpr TypelessObjectWithRefCounter() = default;virtual constexpr ~TypelessObjectWithRefCounter() = default;constexpr TypelessObjectWithRefCounter(const TypelessObjectWithRefCounter&) = delete;constexpr TypelessObjectWithRefCounter(TypelessObjectWithRefCounter&&) = delete;constexpr TypelessObjectWithRefCounter& operator=(const TypelessObjectWithRefCounter&) = delete;constexpr TypelessObjectWithRefCounter& operator=(TypelessObjectWithRefCounter&&) = delete;};template<typename T>struct RefCountingSmartPointer{TypelessObjectWithRefCounter* ptr_to_ref = nullptr; // contains storage for the actual object (might be different type than T)T* ptr_to_object = nullptr; // typed pointer to objectconstexpr T* get() const { return ptr_to_object; }constexpr T* operator*() const { return get(); }constexpr T* operator->() const { return get(); }constexpr void AddRef() const{if(ptr_to_ref != nullptr) ptr_to_ref->AddRef();}constexpr void DecRef() const{if (ptr_to_ref != nullptr) {ptr_to_ref->DecRef();if (ptr_to_ref->IsExpired()) {delete ptr_to_ref;}}}constexpr RefCountingSmartPointer() = default;constexpr RefCountingSmartPointer(TypelessObjectWithRefCounter* ptr_to_ref_, T* ptr_to_object_): ptr_to_ref(ptr_to_ref_),ptr_to_object(ptr_to_object_){}constexpr ~RefCountingSmartPointer(){DecRef();}// templated copy constructor, allows pointer conversion to work the same as with regular pointerstemplate<typename F> requires PointerConvertible<T, F>constexpr RefCountingSmartPointer(const RefCountingSmartPointer<F>& copy): ptr_to_ref(copy.ptr_to_ref),ptr_to_object(copy.ptr_to_object){AddRef();}template<typename F> requires PointerConvertible<T, F>constexpr RefCountingSmartPointer& operator=(const RefCountingSmartPointer<F>& copy){copy.AddRef();auto temp_ref = copy.ptr_to_ref;ptr_to_object = copy.ptr_to_object;DecRef();ptr_to_ref = temp_ref;}// move constructor/assignment omitted for brevityconstexpr RefCountingSmartPointer<std::remove_const_t<T>> clone() const{// returns a smart pointer to a new copy of whatever this points to (if it has a copy constructor)if (ptr_to_ref == nullptr) return {};auto ref_copy = ptr_to_ref->clone();if (ref_copy == nullptr) throw std::logic_error("trying to clone an object that is not copyable");// tricky part is getting the ptr_to_objectTypeErasedPointer<std::remove_const_t<T>> te_ptr_to_cloned_obj;ref_copy->SetTypelessToPointToObject(&te_ptr_to_cloned_obj);return RefCountingSmartPointer<std::remove_const_t<T>>(ref_copy, te_ptr_to_cloned_obj.typed_pointer);}};template<typename ...Types>struct SampleTypeList {};template<typename T>struct TypeErasedObject : TypelessObjectWithRefCounter{T obj;template<typename...Args>constexpr TypeErasedObject(Args&&... args): obj(std::forward<Args>(args)...){}constexpr TypelessObjectWithRefCounter* clone() const override;constexpr void SetTypelessToPointToObject(TypelessPointer* teptr) override{//using BaseList = typename std::bases<T>::type; // would like to do thisusing namespace TestClasses;using BaseList = SampleTypeList<A, B, C, D>; // hardcode typelist, since we don't have std::bases// this will go through the listed types (at runtime) and find the right one and set the pointerBaseListUnpacker<T, BaseList>::SetTyplessPointer(teptr, std::addressof(obj));}};inline constexpr TypelessObjectWithRefCounter* DoCloneRef(...){//no copy constructor versionreturn nullptr;}template<Copyable T>inline constexpr TypelessObjectWithRefCounter* DoCloneRef(const T& t){return new TypeErasedObject<T>(t);}template<typename T>constexpr TypelessObjectWithRefCounter* TypeErasedObject<T>::clone() const{return DoCloneRef(obj);}/****** Provide a convenient interface for our smart pointer ******/template<typename T>using P = RefCountingSmartPointer<const T>;template<typename T>using Pm = RefCountingSmartPointer<T>;template<typename T, typename ...Args>constexpr Pm<T> Make(Args&&... args){TypeErasedObject<T>* pteo = new TypeErasedObject<T>(std::forward<Args>(args)...);return Pm<T>(pteo, std::addressof(pteo->obj));}/****** Test main ******/int main(){using namespace TestClasses;P<A> pa = Make<A>();P<A> pb = Make<B>();P<A> pc = Make<C>();P<A> pd = Make<D>();Pm<A> pa_copy = pa.clone(); //worksPm<A> pb_copy = pb.clone(); //worksPm<A> pc_copy = pc.clone(); //worksPm<A> pd_copy = pd.clone(); //worksP<C> pcd = Make<D>();Pm<C> pcd_copy = pcd.clone(); // also workspcd_copy->a = 42;pcd_copy->c = 137;std::cout << pcd->a << " = 0\n";std::cout << pcd->c << " = 2\n";std::cout << pcd->get() << " = 3\n";std::cout << pcd_copy->a << " = 42\n";std::cout << pcd_copy->c << " = 137\n";std::cout << pcd_copy->get() << " = 3\n";}On Tue, Jan 31, 2023 at 12:27 PM Arthur O'Dwyer <arthur.j.odwyer@gmail.com> wrote:Can you please show some example code? Just write the exact code you think you want to write, but pretend that `std::bases_of<T>::type` already exists. Show how you'll solve your problem using `std::bases_of<T>::type`.Thanks,Arthur