15

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
plexando
  • 1,151
  • 6
  • 22

2 Answers2

11

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
8

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.

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160