Search code examples
c++lvaluervalue

invalid initialization of non-const reference of type from an rvalue of type


I am writing some code based on issue 28 smart pointer of more effective c++ as follows. However, it cannot compile:

main.cpp: In instantiation of 'SmartPointer<T>::operator SmartPointer<U>() [with U = MusicProduct; T = Cassette]':
main.cpp:99:17:   required from here
main.cpp:48:39: error: invalid initialization of non-const reference of type 'SmartPointer<MusicProduct>&' from an rvalue of type 'SmartPointer<MusicProduct>'
       return SmartPointer<U> (ptr_);
                                   ^
main.cpp:16:9: note:   initializing argument 1 of 'SmartPointer<T>::SmartPointer(SmartPointer<T>&) [with T = MusicProduct]'
     SmartPointer(SmartPointer<T>& other)
     ^

Either of these two changes works:

  1. in the implementation of operator SmartPointer (), create an object and return:

    SmartPointer a(ptr_);

    return a;

  2. Or, make the parameter of the copy constructor as const

    SmartPointer(const SmartPointer& other)

    and comment the line

    other.ptr_ = nullptr;

Is there any reason why either of the solutions works? Thanks.


#include <iostream>

template <typename T>
class SmartPointer
{
  public:
    SmartPointer(T* ptr) : ptr_(ptr) {}
    ~SmartPointer()
    {
      if (ptr_)
      {
        delete ptr_;
      }
    }

    SmartPointer(SmartPointer<T>& other)
    {
      ptr_ = other.ptr_;
      other.ptr_ = nullptr;
    }

    SmartPointer<T>& operator = (SmartPointer<T>& other)
    {
      if (this == &other)
      {
        return *this;
      }

      if (ptr_)
      {
        delete ptr_;
      }

      ptr_ = other.ptr_;
      other.ptr_ = nullptr;

      return *this;
    }

    template <typename U>
    operator SmartPointer<U> ()
    {
      // it works
      //SmartPointer<U> a(ptr_);
      //return a;

      // error
      return SmartPointer<U> (ptr_);
    }

    T& operator * () const
    {
      return *ptr_;
    }

    T* operator -> () const
    {
      return ptr_;
    }

  private:
    T* ptr_ = nullptr;
};

class MusicProduct
{
public:
  MusicProduct(const std::string& name) : name_(name) {}
  virtual ~MusicProduct() {}

  virtual void Play() const = 0;
  virtual void ShowName() const
  {
    std::cout << name_ << std::endl;
  }

private:
  std::string name_;
};
class Cassette : public MusicProduct
{
  public:
  Cassette(const std::string& name) : MusicProduct(name) {}
  void Play () const
  {
    std::cout << "play cassette" << std::endl;
  }
};

void CallPlay(const SmartPointer<MusicProduct>& sp)
{
  sp->Play();
}

int main()
{
  SmartPointer<Cassette> a(new Cassette("Zhang"));
  a->ShowName();
  CallPlay(a);
  return 0;
}

Solution

  • That's because your copy ctor has a non-const reference parameter and therefore cannot accept a temporary. Thus

    return SmartPointer<X>(y);
    

    won't work. The argument to the return keyword is a temporary, and it needs to be copied, so here the design breaks down.

    Since you are using C++11, you can fix this by introducing a move constructor (and move assignment).

    You can also make the argument const and designate the ptr_ member as mutable. This will allow you to copy from temporaries and const smart pointers, but for the price of actually mutating them.