Question

How to return smart pointers and covariance in C++

I am following this tutorial to understand how to return smart pointers and covariance in C++.

  #include <memory>
  #include <iostream>

  class cloneable
  {
  public:
     virtual ~cloneable() {}

     std::unique_ptr<cloneable> clone() const
     {
        return std::unique_ptr<cloneable>(this->clone_impl());
     }

  private:
     virtual cloneable * clone_impl() const = 0;
  };
   
  ///////////////////////////////////////////////////////////////////////////////

  template <typename Derived, typename Base>
  class clone_inherit: public Base
  {
  public:
     std::unique_ptr<Derived> clone() const
     {
        return std::unique_ptr<Derived>(static_cast<Derived *>(this->clone_impl()));
     }

  private:
     virtual clone_inherit * clone_impl() const override
     {
        return new Derived(*this); // getting error here 
     }
  };


  class concrete : public clone_inherit<concrete, cloneable>
  {
    
  };

  int main()
  {
    std::unique_ptr<concrete> c = std::make_unique<concrete>();
  }

When I execute this example I get the following error:

  /tmp/0RmVdQYjfA.cpp: In instantiation of 'clone_inherit<Derived, Base>* clone_inherit<Derived, Base>::clone_impl() const [with Derived = concrete; Base = cloneable]':
  /tmp/0RmVdQYjfA.cpp:30:28:   required from here
  /tmp/0RmVdQYjfA.cpp:32:14: error: no matching function for call to 'concrete::concrete(const clone_inherit<concrete, cloneable>&)'
     32 |       return new Derived(*this);
        |              ^~~~~~~~~~~~~~~~~~
  /tmp/0RmVdQYjfA.cpp:37:7: note: candidate: 'constexpr concrete::concrete()'
     37 | class concrete : public clone_inherit<concrete, cloneable>
        |       ^~~~~~~~
  /tmp/0RmVdQYjfA.cpp:37:7: note:   candidate expects 0 arguments, 1 provided
  /tmp/0RmVdQYjfA.cpp:37:7: note: candidate: 'constexpr concrete::concrete(const concrete&)'
  /tmp/0RmVdQYjfA.cpp:37:7: note:   no known conversion for argument 1 from 'const clone_inherit<concrete, cloneable>' to 'const concrete&'
  /tmp/0RmVdQYjfA.cpp:37:7: note: candidate: 'constexpr concrete::concrete(concrete&&)'
  /tmp/0RmVdQYjfA.cpp:37:7: note:   no known conversion for argument 1 from 'const clone_inherit<concrete, cloneable>' to 'concrete&&'

To fix this error in line 32, I had to return exactly the return pointer to a Derived class as follows:

  //   return  new static_cast<Derived*>(*this); to replace with 
  return new Derived(static_cast<const Derived&>(*this));

Can someone suggest the proper way to fix this?

 2  92  2
1 Jan 1970

Solution

 1

You can add a private implicit or explicit cast operator:

template <typename Derived, typename Base>
class clone_inherit : public Base {
 public:
  std::unique_ptr<Derived> clone() const {
    return std::unique_ptr<Derived>(static_cast<Derived *>(this->clone_impl()));
  }

 private:
  // added here
  explicit operator const Derived &() const {
    return static_cast<const Derived &>(*this);
  }

  virtual clone_inherit *clone_impl() const override {
    return new Derived(*this);  // not getting error here
  }
};

2024-07-18
3CxEZiVlQ

Solution

 1

With C++ 23 explicit object arguments, this pattern becomes very simple:

class cloneable
{
public:
    template<typename Self>
    std::unique_ptr<Self> clone(this const Self& self) const {
        return std::make_unique<Self>(self);
    }
};

This avoids having to specify the derived type, as it is deduced.

2024-07-22
Caleth