13
template <typename T> struct A {
    A(T);
    A(const A&);
};

int main()
{
    A x(42); // #1
    A y = x; // #2
}

As I understand,T for #1 will be deduced using the implicit deduction guide generated from the first ctor. Then x will be initialized using that ctor.

For #2 though, T will be deduced using the copy deduction candidate (which, as I understand, is a specific case of a deduction guide) (and then y will be initialized using the 2nd ctor).

Why couldn't T for #2 be deduced using the (implicit) deduction guide generated from the copy-ctor?

I guess I just don't understand the general purpose of the copy deduction candidate.

ledonter
  • 1,269
  • 9
  • 27
  • 3
    Not sure what you expect or don't understand. `x` and `y` are `A`. – Jarod42 May 11 '18 at 13:03
  • 2
    @Jarod42 The question asks about the process by which it is determined that `y` is `A`, providing two distinct ways how that could be determined, and asking why one way wasn't enough. –  May 11 '18 at 13:05
  • 2
    If #2 would use A::A(T) we would end up with y beeing A>. Therefore A(const A&) must be prefered. – kiloalphaindia May 11 '18 at 13:08
  • *For #2 through, T will be deduced using the copy deduction candidate* How do you get that? – NathanOliver May 11 '18 at 13:09
  • *"Why couldn't T for #2 be deduced using the (implicit) deduction guide generated from the copy-ctor?"* you should've provided an example demonstrating stated behavior. I've tried [this](https://wandbox.org/permlink/djUTFiL2Oj65C2nT), but it works. – user7860670 May 11 '18 at 13:11
  • 1
    @NathanOliver https://en.cppreference.com/w/cpp/language/class_template_argument_deduction#Notes example for tiebreakers, I basically just copied part of it, specifically comment `uses #5 to deduce A and #2 to initialize` (#5 being a copy deduction candidate and #2 a copy-ctor) – ledonter May 11 '18 at 13:11
  • @kiloalphaindia to my understanding A::A(T) wouldn't be considered just because of partial ordering rules (copy-ctor is more 'specialized') and deduction guide tiebreakers wouldn't even come into play – ledonter May 11 '18 at 13:13

2 Answers2

12

The initial draft to add the wording for copy deduction was P0620R0, which mentions

This paper is intended to resolve

  • The direction on wrapping vs. copying from EWG on Monday in Kona

Some notes on that meeting are available on https://botondballo.wordpress.com/2017/03/27/trip-report-c-standards-meeting-in-kona-february-2017/:

Copying vs. wrapping behaviour. Suppose a is a variable of type tuple<int, int>, and we write tuple b{a};. Should the type of b be tuple<int, int> (the "copying" behaviour), or tuple<tuple<int, int>> (the "wrapping" behaviour)? This question arises for any wrapper-like type (such as pair, tuple, or optional) which has both a copy constructor and a constructor that takes an object of the type being wrapped. EWG felt copying was the best default. There was some talk of making the behaviour dependent on the syntax of the initialization (e.g. the { } syntax should always wrap) but EWG felt introducing new inconsistencies between the behaviours of different initialization syntaxes would do more harm than good.

@kiloalphaindia explained this in a comment:

If #2 would use A::A(T) we would end up with y beeing A<A<int>>. [...]

This is right. The A<A<int>>::A(A<int>) constructor has an exact match in the parameter type. On the other hand, you're also right that A<int>::A(const A<int> &) would in this case have been preferred instead.

But consider this alternative, where the function equivalent shows that A<A<int>> would have been preferred if not for the copy deduction candidate:

template <typename T>
struct A {
    A(T &&);
    A(const A<T> &);
};

template <typename T> auto f(T &&) -> A<T>;
template <typename T> auto f(const A<T> &) -> A<T>;

int main() {
  A x1(42);                   // A<int>
  A y1 = std::move(x1);       // A<int>

  auto x2 = f(42);            // A<int>
  auto y2 = f(std::move(x2)); // A<A<int>>
}
Community
  • 1
  • 1
2

The basic issue is that in the general case you don't know if something is a copy/move constructor until you know the template arguments and instantiate the specialization, but for CTAD you don't know the template arguments (duh) and have to go by the declarations alone:

template<bool B, class T>
struct A {
   A(std::conditional_t<B, A, int>&); // copy ctor? maybe
   A(std::conditional_t<!B, A&&, char>); // move ctor?

   T t;
   // A(const A&); // implicitly declared? or not? 
                   // is this even the right signature? (depends on T)
   // A(A&&); // implicitly declared? or not?
};
A a = A<true, int>(); // deduce?

The copy deduction candidate works around this, and also avoid overload resolution subtleties about value category and cv-qualifications by using a by-value parameter.

T.C.
  • 133,968
  • 17
  • 288
  • 421