As the title says, I'm wondering if there's a way to expose a protected constructor in a derived class (that is, to change access from protected to public). Consider the following (contrived) example:
struct A {
int a;
int b;
protected:
A(int, int) {};
void do_something();
};
struct B : A {
B() : A(0, 0) {};
using A::A;
using A::do_something;
};
int main() {
B b(0, 0); // error: 'A::A(int, int)' is protected
//B b{}; // no errors
b.do_something();
}
So one can change the access of protected member functions, but not of constructors? If so, what is the rationale for the restriction?
Workaround: Variadic template with argument forwarding can serve as a workaround that would perform identically.
Speculation for the rationale: by default the constructor of base classes are not "inherited" in the regular sense; the constructor of derived class must construct the base class instance(s) first. When using is used upon regular member functions, it introduces the functions into the current declarative region thus changing access; on constructors, using only brings base constructor to the "normal inheritance" level (identical to other member functions without using). Because only in some cases is constructor inheritance (reuse of base constructors) useful, the standard committee people designated using X::X syntax for constructor inheritance rather than the stronger namespace teleporting.
What you are trying to do is to call the constructor B, with two parameters, but it doesn't take any.
The error you get, is about the constructor of B, being protected, but if you look carefully, that is not the constructor you declared:
error: 'B::B(int, int)' is protected
Why is that?
Because the compiler is trying to resolve your call to something valid, and it finds the base class constructor to match the signature. But it is still protected so it fails.
What you need to do, to "relaunch" the protected constructor to an inherited class is to provide a matching constructor:
struct B : A {
B() : A(0, 0) {};
B(int a, int b) : A(a, b) {};
using A::A;
using A::do_something;
};
int main() {
B b;
B b2(1, 2);
//b.do_something();
}
http://cpp.sh/9tvik
The reason why, you cannot change the accessor level of a constructor the same way you change it for a method, is most probably to be searched into the non-virtual nature of the constructors. [citation needed :)]
Related
I am using Visual Studio 2019 (v16.10.3) with /std:c++latest and this compiles:
class Base
{
public:
Base(int x) {}
};
class Derived : public Base
{
// no constructors declared in Derived
};
int main() {
Derived d(5);
}
For the previous versions of the standard I have to declare inherited constructors with the using directive:
class Derived : public Base
{
using Base::Base;
};
Is this something new that was put in C++20 or is it some Microsoft specific thing?
Is this something new that was put in C++20 or is it some Microsoft specific thing?
Not with relation to inherited constructors. What changed is that aggregate initialization may use parenthesis under certain conditions. Derived is considered an aggregate on account of having no private parts, so we initialize its bases and members directly.
It even works when we add a public member:
class Base
{
public:
Base(int ) {}
};
struct Derived : public Base
{
// no constructors declared in Derived
int y;
};
int main() {
Derived d(5, 4);
}
Live
This is the result of C++17 allowing classes with base classes to still be considered aggregates combined with C++20 rules allowing aggregate initialization to work with () constructor syntax if the values in the parentheses would work (and wouldn't call a constructor). So while it looks like a constructor call, it's actually aggregate initialization.
Derived doesn't have a constructor matching Base's constructor; you're merely initializing the aggregate Derived by providing a valid initializer for its subobject Base.
Consider:
class A
{
protected:
A(int) {}
void f(int) {}
public:
A() {}
};
class B : public A
{
public:
using A::A;
using A::f;
};
int main()
{
B().f(1); // ok
B(1); // error: 'A::A(int)' is protected within this context
}
Why can't an inherited protected constructor be made public, while an inherited protected member function can?
Unlike other members, the accessibility of the using-declaration that introduced the inherited constructor is ignored.
[namespace.udecl]/19,
(emphasis mine)
A synonym created by a using-declaration has the usual accessibility for a member-declaration. 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.
Actually, the inherited constructor can be made public, but not just the way you wrote it. You can define your B class as follows:
class B : public A {
public:
B() {}
B(int x) : A(x) {} // instead of using A::A(int)
using A::f;
};
(see it on GodBolt)
Perhaps the standard committee thought that saying using A::A would be a bit ambiguous, since a constructor of the base class is not exactly the same thing as a constructor of the subclass.
Consider following code:
class TBase {
public:
TBase();
TBase(const TBase &);
};
class TDerived: public TBase {
public:
using TBase::TBase;
};
void f() {
TBase Base;
TDerived Derived(Base); // <=== ERROR
}
so, I have base and derived classes, and want to use "using TBase::TBase" to pull copy ctor from base class to be able to create instance of derived class in such way:
TDerived Derived(Base);
But all compilers rejects this with these error messages
7 : note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'TBase' to 'const TDerived' for 1st argument
Why? What am I doing wrong? Why "using TBase::TBase" does not work in this situation?
UPDATE
How can be explained following piece of code from cppreference.com?
struct B1 {
B1(int);
};
struct D1 : B1 {
using B1::B1;
// The set of inherited constructors is
// 1. B1(const B1&)
// 2. B1(B1&&)
// 3. B1(int)
Copy and move consturctors (and the default constructor) are never inherited, simply because the standard says so. All other constructors are.
That comment on cppreference was misleading(1). The same comment in the standard says:
The candidate set of inherited constructors in D1 for B1 is
(Emphasis mine).
The standard then goes on to say that only the D1(int) constructor is actually inherited. The copy and move constructors for D1 are implicitly-declared as for any other class, not inherited.
Refer to C++14 12.9 [class.inhctor] for details.
(1) I submitted a change to cppreference to hopefully clarify this.
If you further read the same piece of code, it says:
// D1 has the following constructors:
// 1. D1()
// 2. D1(const D1&)
// 3. D1(D1&&)
// 4. D1(int) <- inherited
};
Thus a copy ctor is still the copy ctor, it accepts an argument of class TDerived. D1(int) is generated automatically nevertheless.
As per standard 12.6.3/p1 Initialization by inherited constructor [class.inhctor.init] (Emphasis Mine):
When a constructor for type B is invoked to initialize an object of a
different type D (that is, when the constructor was inherited
(7.3.3)), initialization proceeds as if a defaulted default
constructor were used to initialize the D object and each base class
subobject from which the constructor was inherited, except that the B
subobject is initialized by the invocation of the inherited
constructor. The complete initialization is considered to be a single
function call; in particular, the initialization of the inherited
constructor’s parameters is sequenced before the initialization of any
part of the D object.
Thus, constructors are not actually inherited but rather they're implicitly or explicitly called by the respective derived constructor. Also keep in mind that the inherited constructors are simply calling the base constructors and do not perform any member initialization in the derived object.
To clarify this consider the following example:
struct Base {
Base(int);
...
};
struct Derived : Base {
using Base::Base;
...
};
The above Derived class definition is syntactically equivalent with:
struct Derived : Base {
Derived(int i) : Base(i) {}
...
};
That is, the using declaration in the Derived class implicitly defines the constructor Derived(int). At this point mind also that if the constructor is inherited from multiple base class sub-objects Derived, the program is ill-formed.
In the same manner you've been lead to the logical conclusion that since I've declared in the base class a copy constructor with the using declaration:
class TBase {
public:
TBase();
TBase(const TBase &);
};
class TDerived: public TBase {
public:
using TBase::TBase;
};
I would get the following syntactical equivalent Derived class:
class TDerived: public TBase {
public:
TDerived() : Base() {}
TDerived(TBase const &other) : Base(other) {}
};
However, this is not the case. You can't "inherit" a copy constructor neither a default constructor neither a move constructor. Why? because this is how the C++ standard dictates so.
What you can do instead is to define a user defined constructor that will take as input a base class object:
class TDerived: public TBase {
public:
TDerived(TBase const &other) {}
};
After all TDerived and TBase are different classes even though the first inherits the second one.
It's easier to explain it in code:
class A {
protected:
A(int i) {}
void foo() {}
};
class B : public A {
public:
B() : A(0) {}
using A::A;
using A::foo;
};
int main()
{
B b1;
// [protected] A::foo => [public] B::foo
b1.foo(); // Ok
// [protected] A::A(int) => [protected] B::B(int)
B b2(0); // cannot access protected member
}
I tried the code in VS2015. I could change the access levels of member functions with using declarations, while I couldn't do the same on constructors. That's weird to me. Does anyone have an idea why they design it works like this?
Constructors are not generated in this case, instead they are inherited (actually from a base class).
According with the documentation, when inheriting a constructor:
It has the same access as the corresponding base constructor
Where access indicates access specifiers.
On the other side, for member methods:
Using-declaration introduces a member of a base class into the derived class definition, such as to expose a protected member of base as public member of derived.
In this case, access can be explicitly changed.
That's why you can change the access levels of member functions with using declarations, while you cannot do the same on constructors.
#include<iostream>
using namespace std;
class base
{
protected:
int a;
public:
base(int i)
{
a=i;
}
};
class derived :protected base
{
public:
derived(){}
void show()
{
cout<<a;
}
};
int main()
{
base obj(2);
derived obj1;
obj1.show();
return 0;
}
Why is this program giving error as In constructor derived::derived():
error: no matching function for call to base::base()
Please explain. As i have read in stackoverflow that in case of inheriting as protected, protected members of base class became protected member of derived class. If I am wrong then please share a good link to clear my misconception
Once you define a constructor for any class, the compiler does not generate the default constructor for that class.
You define the parameterized constructor(base(int i)) for base and hence the compiler does not generate the no argument constructor for base.
Resolution:
You will need to define a constructor taking no arguments for base yourself.
Add this to your base class:
base():a(0)
{
}
EDIT:
Is it needed to define default constructor to every class? Or is it necessary to have the constructor that matches the derived type also present in base type?
The answer to both is NO.
The purpose of constructors in C++ is to initialize the member variables of the class inside the constructor. As you understand the compiler generates the default constructor(constructor which takes no arguments) for every class.
But there is a catch, If you yourself define (any)constructor(one with parameters or without) for your class, the compiler does not generate the default constructor for that anymore. The compiler reasoning here is "Huh, this user writes a constrcutor for his class himself, so probably he needs to do something special in the constructor which I cannot do or understand", armed with this reasoning the compiler just does not generate the default no argument constructor anymore.
Your code above and you assume the presence of the default no argument constructor(When derived class object is created). But since you already defined one constructor for your base class the compiler has applied its reasoning and now it refuses to generate any default argument constructor for your base class. Thus the absence of the no argument constructor in the base class results in the compiler error.
You must give a value to the base constructor:
class Derived : protected base
{
public:
Derived() : base(0)
{
}
};
Depending on your implementation, give the value to the base constructor. However, you may want the Derived constructor to take also int as an argument, and then pass it to the base one.
You need to implement an empty constructor in base or invoke the defined base(int) constructor explicitly from derived c-tor. Without it, when derived c'tor is activated,
it is trying to invoke base() [empty c'tor], and it does not exist, and you get your error.
You haven't defined a default constructor for Base..
When you instantiate an instance of Derived, it will call the Derived() constructor, which will in turn try to call the Base() default constructur which doesn't exist as you haven't defined it.
You can either declare an empty constructor for base Base::Base() or call the existing one as below:
#include<iostream>
using namespace std;
class base
{
protected:
int a;
public:
base(int i)
{
a=i;
}
};
class derived :protected base
{
derived(): Base(123) {} --this will call the Base(int i) constructor)
void show()
{
cout<<a;
}
};
int main()
{
base obj(2);
derived obj1;
obj1.show();
return 0;
}