2

I have code like below which looks a little bit Confusing. I define a template class. It has a user-defined constructor. When I declare two objects of this template class by "operator =", its user-defined contructor is called to my surprise. Besides, After deleting its copy constructor, the compiling cannot even pass during resolution of "operator =". Does templete constructors have different rules than non-templete class?

#include "iostream"
using namespace std;

template <typename T>
class MyPtr
{
private:
    T p_;
public:
    MyPtr(T P = NULL): p_(P)
    {
        cout<<"track!!"<<endl;
    }

    //MyPtr(const MyPtr<T> & P) = delete;
    ~MyPtr()
    {
 
    }
};

int main()
{
    int i=3;
    int j=4;
    MyPtr<int> p = i;
    MyPtr<int> pp = j;
}
Caiyi Zhou
  • 143
  • 1
  • 9
  • This question is completely unrelated to templates. It behaves the same without templates: https://wandbox.org/permlink/wBzEtZTdeuPTK6NB – Thomas Sablik Mar 01 '21 at 09:20
  • @ThomasSablik I found it as well. I cannot understand why MyPtr p = i atcuall use MyPtr(T P = NULL) to construct a object ,why not default "operator =" – Caiyi Zhou Mar 01 '21 at 09:30
  • 1
    `MyPtr p = i` calls the copy constructor: https://en.cppreference.com/w/cpp/language/copy_constructor _"The copy constructor is called whenever an object is initialized (by direct-initialization or copy-initialization) from another object of the same type (unless overload resolution selects a better match or the call is elided), which includes initialization: `T a = b;` or `T a(b);`, where `b` is of type `T`; "_ That means `i` is implicitly converted to `MyPtr` utilizing `MyPtr(T P = NULL)` and then the copy constructor is called (or at least it has to exist) (until C++17). – Thomas Sablik Mar 01 '21 at 09:39

1 Answers1

4

Does templete constructors have different rules than non-templete class?

No.

MyPtr<int> p = i; (and MyPtr<int> pp = j;) is copy initialization. Note that this is initialization but not assignment, as the effect p is initialized by the constructor MyPtr::MyPtr(T). e.g.

MyPtr<int> p = i; // initialization; constructors get called
p = j;            // assignment; assignment operators (operator=) get called

Before C++17, i would be converted to MyPtr<int> via MyPtr::MyPtr(T) firstly, then the conversion result, i.e. the temporary MyPtr<int> is copied/moved to initialize p; even the copy/move operation is allowd to be optimized, the copy/move constructor is required to be accessible. Declaring copy constructor as delete makes copy constructor unusable and move constructor won't be generated, thus makes MyPtr<int> p = i; ill-formed until C++17.

Since C++17 the optimization is mandatory and the copy/move constructor is not required to be accessible again, that means your code would compile fine with C++17 mode even declaring copy constructor as delete.

  • If T is a class type, and the cv-unqualified version of the type of other is not T or derived from T, or if T is non-class type, but the type of other is a class type, user-defined conversion sequences that can convert from the type of other to T (or to a type derived from T if T is a class type and a conversion function is available) are examined and the best one is selected through overload resolution. The result of the conversion, which is a prvalue temporary (until C++17) prvalue expression (since C++17) if a converting constructor was used, is then used to direct-initialize the object. The last step is usually optimized out and the result of the conversion is constructed directly in the memory allocated for the target object, but the appropriate constructor (move or copy) is required to be accessible even though it's not used. (until C++17)
songyuanyao
  • 169,198
  • 16
  • 310
  • 405