简体   繁体   中英

Using declaration of constructors compromises access specifiers and isn't consistent with other types of members

Today I've learned a new shocking reality, all popular compilers (those I could put my hands on, on godbolt.org) are good with this code (it compiles) and I can't explain why:

class A
{
protected:
    A()
    { }
};

class B : private A
{
    using A::A;
};

int main()
{
    auto b = B{};
    return 0;
}

view on godbolt.org

My reasoning: it should fail at auto b = B{}; , since using declaration is in private scope, therefore that's where the constructors, implicitly provided by the compiler, as the result of that using , should go.

Be it any other member: function or variable, it's access modifier would be determined, based on where is the using declaration is placed ( public / protected / private section).

But, now, this doesn't compile:

class A
{
protected:
    A(int)
    { }
};

class B : private A
{
    using A::A;
};

int main()
{
    auto b = B{1};
    return 0;
}

view on godbolt.org

And that's predictable and intuitive:

<source>:15:14: error: calling a protected constructor of class 'A'
    auto b = B{1};
             ^
<source>:4:5: note: declared protected here
    A(int)

But, unfortunately, it doesn't compile (as IS intuitive I believe) for other reasons, because this doesn't as well:

class A
{
protected:
    A(int)
    { }
};

class B : public A
{
public:
    using A::A;
};

int main()
{
    auto b = B{1};
    return 0;
}

view on godbolt.org


It seems that using declaration is either badly worded or badly understood. Unfortunately, many compilers (some of them, fortunately, not longer, in a HEAD) also struggle with friend permissions:

class C;

class A
{
    friend class C;

protected:
    A(int)
    { }
};

class B : public A
{
public:
    using A::A;
};

class C
{
public:
    B make_b()
    {
        return B{1};
    }
};

int main()
{
    auto b = C{}.make_b();
    return 0;
}

view on godbolt.org

Can some language lawyer analyse this and shed some light? Am I wrong with my assumptions and this is how it should be?

class.default.ctor

If there is no user-declared constructor for class X, a non-explicit constructor having no parameters is implicitly declared as defaulted

There are no user-declared constructors for class B . The constructor that B inherits from A is not a constructor for B , it is a constructor for A . Inherited constructors are considered when looking up constructors for the derived class, but they are still not constructors for the derived class.

The standard never explicitly says that inheriting constructors does or does not create similar constructors for the derived class. The standard does say that the constructors for the base class are made available for lookup and overload resolution as if they were constructors for the derived class. This IMO means that they are not considered constructors for the derived class, though it would be better if the standard explicitly said that. At any rate, the compilers seem to interpret it this way.

Edit This is a change from C++14 , where inherited constructors were injected ino the derived class. Even in C+14, these constructors were implicitly declared and not user-declared.

Thus B has a public implicitly declared as defaulted default constructor, regardless of whatever it inherits from A .

namespace.udecl

Base-class constructors considered because of a using-declarator are accessible if they would be accessible when used to construct an object of the base class; the accessibility of the using-declaration is ignored.

Thus A::A(int) is not accessible when constructing B , even though the using declaration that imports it is accessible.

The problem with the using statement if that it only imports the declarations of constructors but will not change their visibility in the sense public, protected, private.

A more robust way would be to declare a public ctor:

class B : public A
{
public:
    B(int i): A(i){};
};

That way you use a protected member from an explicitely declared public one.


Reference:

The n4860 draft for C++20 says at 9.9 The using declaration [namespace.udecl] §1

...If the using-declarator does not name a constructor, the unqualified-id is declared in the declarative region in which the using-declaration appears as a synonym for each declaration introduced by the using-declarator. If the using-declarator names a constructor, it declares that the class inherits the set of constructor declarations introduced by the using-declarator from the nominated base class.

Only non constructors would have been declared public that way.

The reason is the compiler will add a default public non-parameter constructor if there is no one in the class. Your first case will be just like this after compile:

class A
{
protected:
    A()
    { }
};

class B : private A
{
    using A::A;
public:
    B() : A() {}
};

int main()
{
    auto b = B{};
    return 0;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM