Consider the following code
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;
}
which yields
mctor from 2
mctor from 3
So it basically looks as if the externally defined constructor perfectly forwards the value category of its argument while the inline-defined constructor does not.
Is it that an externally defined constructor is handled like a function template (which perfectly forwards its arguments) or what's going on here?
Links to the appropriate section of the standard would be welcome.
I am using GCC 7.2.0.
What is Perfect Forwarding. Perfect forwarding allows a template function that accepts a set of arguments to forward these arguments to another function whilst retaining the lvalue or rvalue nature of the original function arguments.
std::forward has a single use case: to cast a templated function parameter (inside the function) to the value category (lvalue or rvalue) the caller used to pass it. This allows rvalue arguments to be passed on as rvalues, and lvalues to be passed on as lvalues, a scheme called “perfect forwarding.”
The std::forward function as the std::move function aims at implementing move semantics in C++. The function takes a forwarding reference. According to the T template parameter, std::forward identifies whether an lvalue or an rvalue reference has been passed to it and returns a corresponding kind of reference.
When t is a forwarding reference (a function argument that is declared as an rvalue reference to a cv-unqualified function template parameter), this overload forwards the argument to another function with the value category it had when passed to the calling function.
It's a GCC bug. Forwarding references have a very clear cut definition:
[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.
In both cases T
names a template parameter of the enclosing class during CTAD, so it should not produce a forwarding reference either way. The c'tor being defined inline or outside the class definition has no bearing on this.
It looks that GCC incorrectly treats T&&
in an auto-generated deduction guide as a forwarding reference:
template <typename T>
B2(T&& x) -> B2<T>;
In this case T&&
is a non-forwarding r-value reference, because it's a class parameter. Instead, GCC incorrectly deduces T=A&
parameter type and B2<T>=B2<A&>
class type, which collapses the reference type in the constructor, allowing the code to compile with an lvalue constructor argument:
constexpr B2(A& x) noexcept;
Class template argument deduction makes no distinction between inline and and out-of-line definitions. In this particular case, B2 b21 { a };
should fail.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With