Search code examples
c++templatesc++17implicit-conversion

How to write conversion from Foo<T> to Foo<T const>?


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;
}

Solution

  • 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;
    };