On Wed, May 8, 2024 at 2:05 AM David wang via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Tue, 7 May 2024 10:36:25 +0200 Sebastian Wittmeier  wrote:
>So in your case, you want to have a reference type deduced (by template argument deduction),

>but currently [expr.type]/1 disagrees (https://timsong-cpp.github.io/cppwp/expr.type#1): >If an expression initially has the type ?reference to T? ([dcl.ref], [dcl.init.ref]), the type is >adjusted to T prior to any further analysis.

>The copy constructor is something special (compared to other constructors).
>Why do you want to handle it by a template
>in the first place?

Yes, I want to have a reference type deduced, but not  through the template parameter deduction mechanism PR87332.  The template parameter deduction mechanism leads to T, not T&. "template <typename T> base(T x) {}" prevents the compiler from calling the default copy constructor. 

No it doesn't.
Constructor templates are specifically excluded from being copy constructors or move constructors. This exclusion causes problems, but it also means that your `class Base` totally does have an implicitly defaulted copy constructor, separate from the constructor template that you're trying not to call.

So your code was:

struct Base {
  int b;
  template<class T> Base(T);
  // here the compiler also automatically generates Base(const Base&)=default
};
struct Derived : Base {
  int d;
  Derived(const Derived& rhs) : Base(rhs), d(rhs.d) {}  // manually calls Base::Base<Derived>(Derived)
};
Derived copy(const Derived& rhs) { return rhs; }  // infinite recursion at runtime

This code is buggy. But it also violates the Rule of Zero: you wrote code manually, and you got it wrong, so naturally it's also going to do the wrong thing. If you use the Rule of Zero, then what you have is:

struct Base {
  int b;
  template<class T> Base(T);
  // here the compiler also automatically generates Base(const Base&)=default
};
struct Derived : Base {
  int d;
  Derived(const Derived& rhs) = default;  // automatically calls Base::Base(const Base&)
};
Derived copy(const Derived& rhs) { return rhs; }  // OK

So this leads us to a general rule of thumb:
- Always follow the Rule of Zero. Manually implementing special member functions is tricky and should be avoided when possible.

And it leads us to a specific workaround in this case. If we really must take control of `Derived`'s value semantics — if we can't delegate them to some other "resource-management type" — then we must take the responsibility for ensuring that the correct code is run for each thing that we manually do.

struct Base {
  int b;
  template<class T> Base(T);
  // here the compiler also automatically generates Base(const Base&)=default
};
struct Derived : Base {
  int d;
  Derived(const Derived& rhs) : Base((const Base&)rhs), d(rhs.d) {}  // manually calls Base::Base(const Base&)
};
Derived copy(const Derived& rhs) { return rhs; }  // OK

I notice in passing that both GCC and Clang generate worse codegen for the manual approach than they do for the Rule-of-Zero approach. So that's another reason to prefer the Rule of Zero.

HTH,
–Arthur