Inheritance of copy constructors in C++17
Asked Answered
F

2

14

Consider the following example:

struct Parent
{
    Parent ();

    Parent (const Parent &);
};

struct Child : public Parent
{
    using Parent::Parent;
};

Parent p;

Child c (p);

This was taken from the following question: Why "an inherited constructor is not a candidate for initialization from an expression of the same or derived type"?

That original question asked about C++11. In C++11, there is wording that prevents Child from acquiring a constructor that takes const Parent&:

For each non-template constructor in the candidate set of inherited constructors other than a constructor having no parameters or a copy/move constructor having a single parameter, a constructor is implicitly declared with the same constructor characteristics unless there is a user-declared constructor with the same signature in the class where the using-declaration appears.

N4429 significantly changed the specification of inheriting constructors and was considered retroactive to C++11 (I think?). The purpose of N4429 was to cause the base class constructors to be visible as if they were derived class constructors, rather than declaring derived class constructors that delegate to base class constructors. In the first version of N4429, there is the following wording, keeping the restriction from C++11:

When a using-declaration declares that a class inherits constructors from a base class, the default constructor, copy constructor, and move constructor (if any) of the base class are excluded from the set of introduced declarations.

However, in the updated version of this paper, P0136R0, this wording is no longer present, and no explanation is given as to why. The paper was revised once more and then merged into the standard. So in C++17, I cannot see any rule that would prevent the above code from compiling.

Nonetheless, GCC and Clang both reject it. Clang says:

an inherited constructor is not a candidate for initialization from an expression of the same or derived type

However, I cannot find anything in the standard that says anything like this.

Is this code ill-formed in C++17? And if so, why?

Forester answered 13/9, 2019 at 15:57 Comment(1)
The funny thing is I answered this question and this question and still couldn't find the rule...Lefton
D
10

[over.match.funcs]/8:

A constructor inherited from class type C ([class.inhctor.init]) that has a first parameter of type “reference to cv1 P” (including such a constructor instantiated from a template) is excluded from the set of candidate functions when constructing an object of type cv2 D if the argument list has exactly one argument and C is reference-related to P and P is reference-related to D.

See CWG2356.

Dodson answered 13/9, 2019 at 16:18 Comment(4)
This is to be considered as retroactive to C++17, right?Forester
Yes, it's a DR.Dodson
Would you happen to know why the resolution was not to simply resurrect the deleted paragraph preventing such a constructor from being inherited? Does this resolution somehow allow those constructors to remain visible in some other context?Forester
You can't tell what a constructor template ends up looking like until overload resolution time.Dodson
T
1

I think the rule from WG21 is:

Base class copy and move constructors brought into a derived class via a using-declaration are not considered by overload resolution when constructing a derived class object.

If one of the inherited constructors of Base happens to have the signature that matches a copy/move constructor of the Derived, it does not prevent implicit generation of Derived copy/move constructor.

Note that, using A::A; directive inherits all constructors including copy/move and compiler-generated default constructors of A into the derived class(s) where this directive appears; and all constructors of A, except copy/move and compiler-generated default constructors, are considered when looking up the constructors of derived class or when forming set of candidates.

struct B { B(); };
struct D : B { using B::B; };
D d{ B() }; // error: overload resolution found nothing. #1
D d{ D() }; // OK #2

The default copy constructor B::B(const B&) is inherited into D but this constructor is not a candidate function. Without the using directive, the program is well-formed because this is considered an aggregate initialization. Moreover using-declaration doesn't prevent the compiler from generating default copy/move constructors of D, that's why #2 is OK.

Now to suppress this error, we shall be explicitly provide a user-provided copy constructor:

struct B{
    B();
};

struct D : public B{
    using B::B;
    B(const B&);
};

D d{ B() }; // OK: overload resolution found constructor matches the argument-list.
Thread answered 2/5, 2022 at 11:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.