Date: Tue, 31 Jan 2023 18:53:29 -0800
/* This will demonstrate a simple reference-counting smart pointer with
clone() functionality,
that could be implemented using experimental std::bases
Permission hereby granted to use this code for any purpose that will
directly or indirectly
lead 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 time
bp = 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, also
TryToSafelyAssignTypelessPointer<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 along
static 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 allocations
long 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::bases
constexpr 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 object
constexpr 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 pointers
template<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 brevity
constexpr 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_object
TypeErasedPointer<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 this
using 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 pointer
BaseListUnpacker<T, BaseList>::SetTyplessPointer(teptr,
std::addressof(obj));
}
};
inline constexpr TypelessObjectWithRefCounter* DoCloneRef(...)
{
//no copy constructor version
return 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(); //works
Pm<A> pb_copy = pb.clone(); //works
Pm<A> pc_copy = pc.clone(); //works
Pm<A> pd_copy = pd.clone(); //works
P<C> pcd = Make<D>();
Pm<C> pcd_copy = pcd.clone(); // also works
pcd_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_at_[hidden]>
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
>
clone() functionality,
that could be implemented using experimental std::bases
Permission hereby granted to use this code for any purpose that will
directly or indirectly
lead 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 time
bp = 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, also
TryToSafelyAssignTypelessPointer<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 along
static 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 allocations
long 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::bases
constexpr 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 object
constexpr 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 pointers
template<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 brevity
constexpr 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_object
TypeErasedPointer<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 this
using 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 pointer
BaseListUnpacker<T, BaseList>::SetTyplessPointer(teptr,
std::addressof(obj));
}
};
inline constexpr TypelessObjectWithRefCounter* DoCloneRef(...)
{
//no copy constructor version
return 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(); //works
Pm<A> pb_copy = pb.clone(); //works
Pm<A> pc_copy = pc.clone(); //works
Pm<A> pd_copy = pd.clone(); //works
P<C> pcd = Make<D>();
Pm<C> pcd_copy = pcd.clone(); // also works
pcd_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_at_[hidden]>
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
>
Received on 2023-02-01 02:53:42