构造函数中的完美转发 (C++17)

Perfect forwarding in constructors (C++17)

考虑以下代码

struct A {
    A(int id) : id_ { id } {}

    A(const A& rhs) { std::cout << "cctor from " +
        std::to_string(rhs.id_) << std::endl; }
    A(A&& rhs) { std::cout << "mctor from " +
        std::to_string(rhs.id_) << std::endl; }

    int id_;
};

template<typename T>
struct B1 {
    constexpr B1(T&& x) noexcept : x_ { std::forward<T>(x) } {}

    T x_;
};

template<typename T>
struct B2 {
    constexpr B2(T&& x) noexcept;

    T x_;
};

template<typename T>
constexpr
B2<T>::B2(
    T&& x
) noexcept :
    x_ { std::forward<T>(x) } {
}

int
main(
) {
    A a { 1 };

    //B1 b11 { a }; // Not compiling
    B1 b12 { A { 2 } };

    B2 b21 { a };
    B2 b22 { A { 3 } };

    return 0;
 }

产生

mctor from 2
mctor from 3

所以基本上看起来好像外部定义的构造函数完美地转发了它的参数的值类别,而内联定义的构造函数没有。

是外部定义的构造函数像函数模板一样处理(完美转发它的参数)还是这里发生了什么?

欢迎链接到标准的相应部分。

我正在使用 GCC 7.2.0。

这是一个 GCC 错误。转发引用有一个非常明确的定义:

[temp.deduct.call] (emphasis mine)

3 A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template (during class template argument deduction ([over.match.class.deduct])). If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.

在这两种情况下,T 在 CTAD 期间命名了封闭 class 的模板参数,因此它不应以任何一种方式产生转发引用。内联或在 class 定义之外定义的 c'tor 与此无关。

看起来 GCC 错误地将自动生成的推导指南中的 T&& 视为转发参考:

template <typename T>
B2(T&& x) -> B2<T>;

在这种情况下,T&& 是一个非转发 r 值引用,因为它是一个 class 参数。相反,GCC 错误地推断出 T=A& 参数类型和 B2<T>=B2<A&> class 类型,这会折叠构造函数中的引用类型,从而允许代码使用左值构造函数参数进行编译:

constexpr B2(A& x) noexcept;

Class 模板参数推导不区分行内和行外定义。在这种特殊情况下,B2 b21 { a }; 应该会失败。