How useful would Inheriting Constructors be in C++?
As I sit in the C++ Standards committee meetings, they are discussing the pros and cons of dropping Inheriting Constructors since no compiler vendor has implemented it yet (the sense being users haven't been asking for it).
Let me quickly remind everyone what inheriting constructors are:
struct B
{
B(int);
};
struct D : B
{
using B::B;
};
Some vendors are proposing that with r-value references and variadic templates (perfect forwarding constructors), it would be trivial to provide a forwarding constructor in the inheriting class that would obviate inheriting constructors.
For e.g.:
struct D : B
{
template<class ... Args>
D(Args&& ... args) : B(args...) { }
};
I have two questions:
1) Can you provide real world (non-contrived) examples from your programming experience that would benefit significantly from inheriting constructors?
2) Are there any technical reason开发者_JS百科s you can think of that would preclude "perfect forwarding constructors" from being an adequate alternative?
Thanks!
2) Are there any technical reasons you can think of that would preclude "perfect forwarding constructors" from being an adequate alternative?
I have shown one problem with that perfect forwarding approach here: Forwarding all constructors in C++0x .
Also, the perfect forwarding approach can't "forward" the expliciteness of base-class constructors: Either it is always a converting constructor or never, and the base-class will always be direct initialized (always making use of all constructors, even explicit ones).
Another problem are initializer-list constructors because you can't deduce Args
to initializer_list<U>
. Instead, you would need to forward to the base with B{args...}
(note the braces) and initialize D
objects with (a, b, c)
or {1, 2, 3}
or = {1, 2, 3}
. In that case, Args
would be the element types of the initializer list, and forward them to the base class. A initializer-list constructor can then receive them. This seems to cause unnecessary code bloat because the template argument pack will potentially contain lots of type sequences for each different combination of types and length and because you have to choose an initialization syntax this means:
struct MyList {
// initializes by initializer list
MyList(std::initializer_list<Data> list);
// initializes with size copies of def
MyList(std::size_t size, Data def = Data());
};
MyList m{3, 1}; // data: [3, 1]
MyList m(3, 1); // data: [1, 1, 1]
// either you use { args ... } and support initializer lists or
// you use (args...) and won't
struct MyDerivedList : MyList {
template<class ... Args>
MyDerivedList(Args&& ... args) : MyList{ args... } { }
};
MyDerivedList m{3, 1}; // data: [3, 1]
MyDerivedList m(3, 1); // data: [3, 1] (!!)
A couple drawbacks to the proposed workaround:
- It's longer
- It's got more tokens
- It uses brand new complicated language features
Overall, the cognitive complexity of the workaround is very very bad. Much worse than e.g. defaulted special member functions, for which a simple syntax was added.
Real-world motivation for constructor inheritance: AOP mix-ins implemented using repeated inheritance instead of multiple inheritance.
In addition to what others have said, consider this artifical example:
#include <iostream>
class MyString
{
public:
MyString( char const* ) {}
static char const* name() { return "MyString"; }
};
class MyNumber
{
public:
MyNumber( double ) {}
static char const* name() { return "MyNumber"; }
};
class MyStringX: public MyString
{
public:
//MyStringX( char const* s ): MyString( s ) {} // OK
template< class ... Args >
MyStringX( Args&& ... args ): MyString( args... ) {} // !Nope.
static char const* name() { return "MyStringX"; }
};
class MyNumberX: public MyNumber
{
public:
//MyNumberX( double v ): MyNumber( v ) {} // OK
template< class ... Args >
MyNumberX( Args&& ... args ): MyNumber( args... ) {} // !Nope.
static char const* name() { return "MyNumberX"; }
};
typedef char YesType;
struct NoType { char x[2]; };
template< int size, class A, class B >
struct Choose_{ typedef A T; };
template< class A, class B >
struct Choose_< sizeof( NoType ), A, B > { typedef B T; };
template< class Type >
class MyWrapper
{
private:
static Type const& dummy();
static YesType accept( MyStringX );
static NoType accept( MyNumberX );
public:
typedef typename
Choose_< sizeof( accept( dummy() ) ), MyStringX, MyNumberX >::T T;
};
int main()
{
using namespace std;
cout << MyWrapper< int >::T::name() << endl;
cout << MyWrapper< char const* >::T::name() << endl;
}
At least with MinGW g++ 4.4.1, compilation fails due to the C++0x constructor forwarding.
It compiles fine with the "manual" forwarding (commented out constructors), and presumably/possibly also with inherited constructors?
Cheers & hth.
I see a problem when the new class has member variables that need to be initialized in the constructor. This will be the common case, as usually a derived class will add some sort of state to the base class.
That is:
struct B
{
B(int);
};
struct D : B
{
D(int a, int b) : B(a), m(b) {}
int m;
};
For those trying to solve it: how do you distinguish between :B(a), m(b)
and :B(b), m(a)
? How do you handle multiple inheritance? virtual inheritance?
If only the most simple case is solved, it will have very limited usefulness in practice. No wonder the compiler vendors haven't implemented the proposal yet.
Philosophically, I'm against inheriting constructors. If you're defining a new class, you're defining how it's going to be created. If most of that construction can take place in the base class, then it's totally reasonable for you to forward that work to the base class' constructor in the initialization list. But you still need to explicitly do it.
精彩评论