I'm writing a class inheriting from std::unique_ptr
for classes having a clone function:
template <typename T>
class cl_ptr : public std::unique_ptr<T>
{
public:
cl_ptr() noexcept = default;
cl_ptr(T* p) noexcept : std::unique_ptr<T>(p) {}
cl_ptr(cl_ptr<T> const& cp) : std::unique_ptr<T>(cp ? cp->clone() : nullptr) {}
cl_ptr(cl_ptr<T>&&) noexcept = default;
cl_ptr<T>& operator=(cl_ptr<T> const& cp) { this->reset(cp ? cp->clone() : nullptr); return *this; }
cl_ptr<T>& operator=(cl_ptr<T>&& cp) noexcept = default;
~cl_ptr() noexcept = default;
};
I have an error when I try to convert from an instance with type T
to an instance with type T const
:
cl_ptr<Foo> p1(new Foo);
cl_ptr<Foo const> p2 = p1; // <- Compiler error here
// error: conversion from ‘cl_ptr<Foo>’ to non-scalar type ‘cl_ptr<const Foo>’ requested
But I don't know how to implement it.
Of course I don't want that this code compiles:
cl_ptr<Foo const> p1(new Foo);
cl_ptr<Foo> p2 = p1; // <- Always wrong
Minimal reproductible example:
# include <memory>
template <typename T>
class cl_ptr : public std::unique_ptr<T>
{
public:
cl_ptr() noexcept = default;
cl_ptr(T* p) noexcept : std::unique_ptr<T>(p) {}
cl_ptr(cl_ptr<T> const& cp) : std::unique_ptr<T>(cp ? cp->clone() : nullptr) {}
cl_ptr(cl_ptr<T>&&) noexcept = default;
cl_ptr<T>& operator=(cl_ptr<T> const& cp) { this->reset(cp ? cp->clone() : nullptr); return *this; }
cl_ptr<T>& operator=(cl_ptr<T>&& cp) noexcept = default;
~cl_ptr() noexcept = default;
};
class Foo
{
public:
Foo() = default;
Foo(Foo const&) = default;
~Foo() noexcept = default;
Foo* clone() const { return new Foo(*this); }
};
int main()
{
cl_ptr<Foo> p1(new Foo);
cl_ptr<Foo const> p2 = p1;
cl_ptr<Foo> p3 = p2; // must fail
return 0;
}
I'm going to allow any conversion of cl_ptr<T>
to cl_ptr<U>
where there is a conversion from T *
to U *
, just like std::unique_ptr
. This allows T *
to const T *
and doesn't allow const T *
to T *
, but also includes Derived *
to Base *
etc.
template <typename T>
class cl_ptr : public std::unique_ptr<T>
{
public:
cl_ptr() noexcept = default;
cl_ptr(T* p) noexcept : std::unique_ptr<T>(p) {}
cl_ptr(cl_ptr<T> const& cp) : std::unique_ptr<T>(cp ? cp->clone() : nullptr) {}
cl_ptr(cl_ptr<T>&&) noexcept = default;
cl_ptr<T>& operator=(cl_ptr<T> const& cp) { this->reset(cp ? cp->clone() : nullptr); return *this; }
cl_ptr<T>& operator=(cl_ptr<T>&& cp) noexcept = default;
template <typename U, typename = typename std::enable_if_t<std::is_convertible_v<U*, T*>>>
cl_ptr(cl_ptr<U> const& cp) : std::unique_ptr<T>(cp ? cp->clone() : nullptr) {}
template <typename U, typename = typename std::enable_if_t<std::is_convertible_v<U*, T*>>>
cl_ptr(cl_ptr<U>&& cp) noexcept : std::unique_ptr<T>(std::move(cp)) {}
template <typename U, typename = typename std::enable_if_t<std::is_convertible_v<U*, T*>>>
cl_ptr<T>& operator=(cl_ptr<U> const& cp) { this->reset(cp ? cp->clone() : nullptr); return *this; }
template <typename U, typename = typename std::enable_if_t<std::is_convertible_v<U*, T*>>>
cl_ptr<T>& operator=(cl_ptr<T>&& cp) noexcept { this->reset(cp.release()); return *this; }
~cl_ptr() noexcept = default;
};