Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perfect forwarding in constructors (C++17)

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.

like image 997
plexando Avatar asked Nov 28 '18 12:11

plexando


People also ask

What is perfect forwarding?

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.

What is the point of std :: forward?

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.”

What is STD forward?

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.

What is forwarding reference in C++?

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.


2 Answers

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.

like image 62
StoryTeller - Unslander Monica Avatar answered Sep 22 '22 16:09

StoryTeller - Unslander Monica


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.

like image 44
Piotr Skotnicki Avatar answered Sep 25 '22 16:09

Piotr Skotnicki