为什么复制演绎候选作为单独的演绎指南是必要的?

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
}

据我所知,T #1 将使用隐式推导指南推导 从第一个 ctor 生成。然后 x 将使用该构造函数进行初始化。

但是对于#2,T 将使用复制推导候选推导(据我了解,这是推导指南的一个特定案例)(然后 y 将被初始化使用第二个 ctor)。

为什么不能使用从 copy-ctor 生成的(隐式)推导指南推导出 #2 的 T

我想我只是不明白copy dynamic candidate的一般用途

最初的草案是P0620R0,其中提到了

This paper is intended to resolve

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

有关那次会议的一些笔记可在 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>>. [...]

没错。 A<A<int>>::A(A<int>) 构造函数的参数类型完全匹配。另一方面,你也是对的,在这种情况下 A<int>::A(const A<int> &) 会更受欢迎。

但是请考虑这个替代方案,其中等价函数表明如果不是复制推导候选者,A<A<int>> 将是首选:

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

基本问题是,在一般情况下,您不知道某个东西是否是 copy/move 构造函数,直到您知道模板参数并实例化专门化,但对于 CTAD,您不知道模板参数(duh)并且必须单独通过声明:

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?

copy deduction candidate 解决了这个问题,并且还通过使用按值参数避免了关于值类别和 cv 限定的重载解析微妙之处。