I have two classes, A and B, and B is derived from A. A has multiple constructors (2 in the example below). B has one additional member to initialize (which has a default initializer).
How can I achieve that B can be construced using one of the constructors of A without having to manually rewrite all the constructor overloads from A in B?
(In the example below, I would otherwise have to provide four constructors for B:B():A(){}
, B(string s):A(s){}
, B(int b):A(),p(b){}
, B(string s, int b):A(s),p(b){}
, instead of just two, at least when ignoring the possibility of default arguments).
My approach was perfect forwarding, however, the following scenario leads to an error:
#include <utility>
#include <string>
struct A {
A(const std::string& a) : name(a) {}
A(){}
virtual ~A(){}
std::string name;
};
struct B : public A {
template<typename... Args>
B(Args&&... args) : A(std::forward<Args>(args)...) {}
B(const std::string& a, int b) : A(a), p(b) {}
int p = 0;
};
int main()
{
B b1("foo");
B b2("foobar", 1);
}
For b2, GCC complains about no matching function for call to 'A::A(const char [5], int)
.
Apparently it is trying to call the perfect forwarding constructor, which obviously shouldn't work, instead of the second constructor of B.
Why doesn't see the compiler the second constructor and call that instead? Are there technical reasons that the compiler can't find the correct constructor of B? How can I fix this behaviour?
The exact error message:
main.cpp: In instantiation of 'B::B(Args&& ...) [with Args = {const char (&)[5], int}]':
main.cpp:26:19: required from here
main.cpp:15:54: error: no matching function for call to 'A::A(const char [5], int)'
B(Args&&... args) : A(std::forward<Args>(args)...) {}
^
main.cpp:6:5: note: candidate: A::A()
A(){}
^
main.cpp:6:5: note: candidate expects 0 arguments, 2 provided
main.cpp:5:5: note: candidate: A::A(const string&)
A(const std::string& a) : name(a) {}
^
main.cpp:5:5: note: candidate expects 1 argument, 2 provided
main.cpp:4:8: note: candidate: A::A(const A&)
struct A {
^
main.cpp:4:8: note: candidate expects 1 argument, 2 provided
"foobar"
is aconst char (&) [7]
. ThereforeArgs
is a better match than aconst std::string&
Thus, this overload is picked:
where
Args
isconst char (&) [7]
so it becomes:
which is forwarded to
A
's 2-argument constructor... which does not exist.something like this...
In a nutshell (and putting it very simply), when overload resolution takes place the compiler does the following:
expand all templated overloads that can possibly match the arguments given. Add them to a list (with a weight indicating the level of specialisation that was involved in getting there).
add any concrete overloads to the list that can be arrived at by legally apply conversion operators to the arguments, with a weight indicating how many conversions are required to turn the supplied arguments into the function argument types.
sort the lists by ascending 'work' or weight.
pick the one that requires least work. If there is a tie for which one is best, error.
The compiler gets one go at this. It's not a recursive search.
My apologies in advance to the purists amongst us who will find this childish explanation offensive :-)
Option #1
Inherit constructors from class
A
:Option #2
Make B's variadic constructor SFINAE-able:
Without going into too much detail, the forwarding constructor is almost always preferred . It may even be preferred to the copy-constructor.
One technique to avoid this ambiguity is to make the caller explicitly select whether they want the forwarding constructor, by using a dummy parameter:
with sample usage:
Then you can even have an
A
and aB
constructor with same parameters.An alternative solution to your problem would be to write
using A::A;
inB
's definition . This is like providingB
with a set of constructors matchingA
's, and they initialize theA
by calling the correspondingA
constructor with the same arguments.Of course this has some drawbacks, e.g. you can't also initialize other
B
members at the same time. Further reading