9

I was looking at this answer regarding What are the rules for automatic generation of move operations?, and am hopeful that the answer has now been well established by now.

The slide that shows what constructors/assignment operators are "not declared", "defaulted" or "deleted", based on what has been declared in the class, shows:

enter image description here

That was taken from these slides, with the red squares meaning that this behaviour is deprecated.

When compiling the following:

#include <iostream>
struct X
{
  template<typename...T>
  X(T&&...) {
    std::cout << "Yay!\n";
  }

  ~X() {}
};

int main() {
  X x0;
  X x1{x0};
  X x2{std::move(x0)};
}

It would appear that they have been "not declared", since it compiles and the output is "Yay!" three times (which is good, at least for me). But I want to confirm that I can rely on this behaviour.

Edit

It has been pointed out by Frank that, if a copy constructor is also added, it still says "Yay!" three times, which is interesting behaviour. Doing further testing, if a move constructor is added, it will only say "Yay!" twice. Can anyone explain this behaviour?

Adrian
  • 10,246
  • 4
  • 44
  • 110
  • "It would appear that they have been "not declared"", not necessarily, that user-defined constructor binds VERY agressively: https://wandbox.org/permlink/BKxKTLY4RUAfhTnC –  May 12 '19 at 15:17
  • @Frank, I'm not sure what you mean. If constructor is not defined, then the template will match, otherwise, it will not. – Adrian May 12 '19 at 15:22
  • @Adrian I meant that your assumption that seeing "Yay!" three times implies that the constructors do not exist is not correct. It just means that they are not used, present or not. In my example, I explicitely add the copy constructor back in, yet it still prints out "Yay!" three times. –  May 12 '19 at 15:24
  • 2
    Your template generates normal, not copy- or move-constructors. A copy/move constructor can never be a template. A catch-all templated constructor is also very, very dangerous, it will match literally anything, and will almost always be a better match. – rustyx May 12 '19 at 15:28
  • Oh, I missed that @Frank. That's indeed interesting. – Adrian May 12 '19 at 15:29
  • @rustyx, yes, but if the copy/move constructors are not defined, then the templated constructor is free to bind in place of the copy/move constructors. As I understand it, if a move/copy constructor was defined/deleted, it should bind to those over any templated function. – Adrian May 12 '19 at 15:39
  • If that's the case @n.m., then what are the rules for when a templated constructor with a universal reference could override a copy/move constructor that exists? – Adrian May 12 '19 at 15:45
  • I have missed the dtor. Sorry about the confusion. Only copy ctor/assignment are defaulted. – n. m. could be an AI May 12 '19 at 15:50
  • Np @n.m.. That's why I added whitespace, to bring attention to the dtor. Need to understand the binding to the template with universal reference over the copy ctor though. – Adrian May 12 '19 at 15:55
  • 2
    Your template ctor is a better match than the copy ctor (a non-const reference is a better match than a const reference). – n. m. could be an AI May 12 '19 at 16:00
  • Oh, of course. @Frank, do you understand? If I change the line 16 in your example from `X x1{x0};` to `X x1{static_cast(x0)};`, it will explicitly call the copy constructor, regardless if you specify the copy ctor or not, since it is already defined as default. – Adrian May 12 '19 at 16:16

1 Answers1

2

According to N4659 (almost C++17 standard), they are still defined as defaulted but the behavior is (still) deprecated.

If the class definition does not explicitly declare a copy constructor, a non-explicit one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted. The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.

If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy assignment operator is defined as deleted; otherwise, it is defined as defaulted. The latter case is deprecated if the class has a user-declared copy constructor or a user-declared destructor.

Oliv
  • 17,610
  • 1
  • 29
  • 72
  • Not sure how [\[class.copy.ctor\]/1](https://timsong-cpp.github.io/cppwp/n4659/class.copy.ctor#1) relates to my example. – Adrian May 12 '19 at 15:51
  • @Adrian Sorry, I did not catch what was your intent. – Oliv May 12 '19 at 15:55
  • @Adrian You can use GCC9 with command line `-Wdeprecated-copy` and `-Wdeprecated-copy-dtor` or just `-Wextra` to get warnings ([demo](https://godbolt.org/z/pyABve)) – Oliv May 12 '19 at 15:57
  • The intent was to get a templated ctor to with a universal reference to bind in place of a copy/move ctor. – Adrian May 12 '19 at 15:58