Domanda

I'm trying to write a conversion operator function template in a class and running into some compile errors which I don't fully understand.

class ABC { };

class BBC:public ABC { };

template <class T>
class TestPtr
{
    public:
        TestPtr(T* ptr=0)
            : _pointee(ptr)
        {   }

        TestPtr(TestPtr& rhs)
        {
            this->_pointee = rhs._pointee;
            rhs._pointee= 0;
        }

        template <class U> operator TestPtr<U>();

    private:
        T* _pointee;
};

template <class T> template <class U>
TestPtr<T>::operator TestPtr<U>()
{
    return TestPtr<U>(this->_pointee);   // if this line is changed to 
    //TestPtr<U> x(this->_pointee);      // these commented lines, the 
    //return x;                          // compiler is happy
}

void foo (const TestPtr<ABC>& pmp)
{  }

int main() {
    TestPtr<BBC> tbc(new BBC());
    foo(tbc);
}

The above code results in the following errors

TestPtr.cpp: In member function ‘TestPtr<T>::operator TestPtr<U>() [with U = ABC, T = BBC]’:
TestPtr.cpp:38:9:   instantiated from here
TestPtr.cpp:28:34: error: no matching function for call to ‘TestPtr<ABC>::TestPtr(TestPtr<ABC>)’
TestPtr.cpp:28:34: note: candidates are:
TestPtr.cpp:13:3: note: TestPtr<T>::TestPtr(TestPtr<T>&) [with T = ABC, TestPtr<T> = TestPtr<ABC>]
TestPtr.cpp:13:3: note:   no known conversion for argument 1 from ‘TestPtr<ABC>’ to ‘TestPtr<ABC>&’
TestPtr.cpp:9:3: note: TestPtr<T>::TestPtr(T*) [with T = ABC]
TestPtr.cpp:9:3: note:   no known conversion for argument 1 from ‘TestPtr<ABC>’ to ‘ABC*’

Now what is baffling to me is that the compiler is trying to pick TestPtr<ABC>::TestPtr(TestPtr<ABC>) instead of TestPtr<ABC>::TestPtr(ABC *) in the return statement. However if I create a variable with the intended constructor first and then return the value it works fine. I also made the T* constructor explicit with no avail.

I've tried with both g++ and clang++ with similar results. Can someone please explain what's going on here?

È stato utile?

Soluzione

The problem is with your copy constructor. Its parameter is a TestPtr& (a non-const reference):

TestPtr(TestPtr& rhs)

A non-const reference cannot bind to an rvalue expression. TestPtr<U>(this->_pointee) is an rvalue expression that creates a temporary object. When you try to return this object directly, a copy must be made. Thus, you get this error when the compiler is unable to call the copy constructor.

Usually the solution would be to have your copy constructor take a const reference. However, in this case, the solution is a bit trickier; you'll want to do something similar to what std::auto_ptr does. I described its dirty tricks in an answer to "How could one implement std::auto_ptr's copy constructor?"

Alternatively, if you are using a recent C++ compiler and only need to support compilers that support rvalue references, you can make your class movable but noncopyable by providing an user-declared move constructor (TestPtr(TestPtr&&)) and by suppressing the copy constructor (declaring it =delete if your compiler supports deleted member functions, or declaring it private and not defining it).


Note also that you must provide a user-declared copy assignment operator. (Or, if you decide to make the type move-only, you'll need to provide a user-declared move assignment operator and suppress the implicit copy assignment operator, again using = delete or by declaring and not defining it.)

Altri suggerimenti

In your copy constructor you for some reason insist on zeroing-out the source pointer

TestPtr(TestPtr& rhs)
{
  this->_pointee = rhs._pointee;
  rhs._pointee= 0;
}

Why are you doing this?

Since you insist on zeroing-out rhs, you cannot declare the argument as const TestPtr& rhs, which in turn breaks your code, as James explained.

I don't see any reason to zero-out the source pointer. Just do

TestPtr(const TestPtr& rhs) : _pointee(rhs._pointee)
  {}

and it should work.

I suspect that you used std::auto_ptr for inspiration and saw something like that in its copying routines. However, in std::auto_ptr the source pointer is zeroed-out because std::auto_ptr takes ownership of the object it points to and transfers that ownership when it gets copied. The need for the ownership management is dictated by the fact that std::auto_ptr not only points to objects, it also attempts to destroy them automatically.

Your pointer does not attempt to destroy anything. It doesn't need to take ownership of the pointed object. For this reason you don't need to zero-out the source when you copy your pointer.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top