Search code examples
c++templatesc++17template-argument-deduction

Why is copy deduction candidate necessary as a separate deduction guide?


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.


Solution

  • 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>>
    }