Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can one default a special member function if one doesn't know its parameter types?

Consider this case:

template<typename T>
struct A {
  A(A ???&) = default;
  A(A&&) { /* ... */ }
  T t;
};

I explicitly declared a move constructor, so I need to explicitly declare a copy constructor if I want to have a non-deleted copy constructor. If I want to default it, how can I find out the correct parameter type?

A(A const&) = default; // or
A(A &) = default; // ?

I'm also interested in whether you encountered a case where such a scenario actually popped up in real programs. The spec says

A function that is explicitly defaulted shall ...

  • have the same declared function type (except for possibly differing ref-qualifiers and except that in the case of a copy constructor or copy assignment operator, the parameter type may be "reference to non-const T", where T is the name of the member function’s class) as if it had been implicitly declared,

If the implicitly-declared copy constructor would have type A &, I want to have my copy constructor be explicitly defaulted with parameter type A &. But if the implicitly-declared copy constructor would have parameter type A const&, I do not want to have my explicitly defaulted copy constructor have parameter type A &, because that would forbid copying from const lvalues.

I cannot declare both versions, because that would violate the above rule for the case when the implicitly declared function would have parameter type A & and my explicitly defaulted declaration has parameter type A const&. From what I can see, a difference is only allowed when the implicit declaration would be A const&, and the explicit declaration would be A &.

Edit: In fact, the spec says even

If a function is explicitly defaulted on its first dec- laration, ...

  • in the case of a copy constructor, move constructor, copy assignment operator, or move assignment operator, it shall have the same parameter type as if it had been implicitly declared.

So I need to define these out-of-class (which I think doesn't hurt, since as far as I can see the only difference is that the function will become non-trivial, which is likely anyway in those cases)

template<typename T>
struct A {
  A(A &);
  A(A const&);
  A(A&&) { /* ... */ }
  T t;
};

// valid!?
template<typename T> A<T>::A(A const&) = default;
template<typename T> A<T>::A(A &) = default;

Alright I found that it is invalid if the explicitly declared function is A const&, while the implicit declaration would be A &:

A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed.

This matches what GCC is doing. Now, how can I achieve my original goal of matching the type of the implicitly declared constructor?

like image 572
Johannes Schaub - litb Avatar asked Feb 25 '23 01:02

Johannes Schaub - litb


1 Answers

I guess I fail to see the problem... how does this differ from the common case of implementing a copy constructor?

If your copy constructor will not modify the argument (and the implicitly defined copy constructor will not do it) then the argument should be passed as a constant reference. The only use case I know of for a copy constructor that does not take the argument by constant reference is when in C++03 you want to implement moving a la std::auto_ptr which is usually a bad idea anyway. And in C++0x moving would be implemented as you have with a move constructor.

like image 133
David Rodríguez - dribeas Avatar answered Apr 28 '23 01:04

David Rodríguez - dribeas