struct B {
void f();
private:
B(int, int = 0);
};
struct D : B { using B::B; };
void B::f() {
auto a = D{0};
auto b = D(0);
auto c = D(0, 0);
D x{0};
D y(0);
D z(0, 0);
}
GCC accepts (since 7.1; previously rejects all).
Clang accepts b
and xyz
but rejects a
and c
.
MSVC rejects all in C++14 mode but accepts all in C++17 mode.
Which compiler(s) are correct? Did the rules change between C++14 and C++17 - if so, why in C++14 is B::f
not allowed to access its own constructors (named via D
)? And why does Clang only accept the (function-style) cast at b
and not list-initialization at a
or constructor call at c
?
(Making B
a friend
of D
makes Clang accept, except for older versions (3.8 and older), and MSVC in C++14 mode. It doesn't make a difference to gcc.)
Now, I know that C++14 says:
A constructor so declared [as an inheriting constructor] has the same access as the corresponding constructor in [the base class] X.
And in C++17 the rules were simplified, clarified and moved to [namespace.udecl]:
A using-declarator that names a constructor does not create a synonym; instead, the additional constructors are accessible if they would be accessible when used to construct an object of the corresponding base class, and the accessibility of the using-declaration is ignored.
So, I thought that perhaps "has the same access as" means the inherited constructors are private
to D
so only accessible to D
(and its friends), rather than private
to B
(and so accessible to B::f
) as are their corresponding constructors in B
. But under that interpretation, one would be able to subvert access checking and access private constructors of one's base class by inheriting them. So surely in C++14 the intent of the wording is the same as expressed more clearly in C++17 - but then why does MSVC change its behavior depending on language version?
There were a lot of issues with the original specification of inheriting constructors (as they were then called), several of which involved access control. They were fixed retroactively, which is supposed to be official for C++14 only (as the most recently published standard at the time). Many compilers choose to apply such changes to even earlier versions where it makes sense (i.e., C++11).
However, it wasn’t possible to access private base-class constructors even in C++14 as published, because the implicit definition of an inheriting private constructor would fail to invoke the actual private constructor in the base. MSVC is therefore correct except for not applying the defect report to C++14; Clang appears to have a separate bug in handling certain kinds of initialization; perhaps it accepts D(0)
because it’s defined in terms of D …(0);
. Current GCC is correct in all cases.