I'm trying to refactor some visitor-pattern code to remove some code duplication. The crux of this task requires splitting function overloads from an existing API into two: some go into a base class whilst others go into a derived class which extends that base.
Upon trying to split the API between the base and derived classes I hit an unexpected compilation error. Attempting to put aside the visitor-related background to this, I've untangled/distilled my problem into the example (c++11) code that follows:
#include <iostream>
class X
{
public:
virtual const char * name() const =0;
};
class Y : public X
{
public:
virtual const char * name() const override { return "Y"; }
};
class Z : public X
{
public:
virtual const char * name() const override { return "Z"; }
};
class APIBase // The API is split between this base class...
{
public:
virtual void foo(Y & y) =0;
};
class APIDerived : public APIBase // ..and this derived class
{
public:
virtual void foo(Z & z) =0;
};
class APIImplementation : public APIDerived
{
public:
virtual void foo(Y & y) override {
std::cout << "foo(" << y.name() << ")" << std::endl;
}
virtual void foo(Z & z) override {
std::cout << "foo(" << z.name() << ")" << std::endl;
}
};
class A
{
public:
APIDerived & api() { return m_api; }
private:
APIImplementation m_api;
};
int main(int argc, char * argv[])
{
Y y;
Z z;
A a;
a.api().foo(y);
a.api().foo(z);
return 0;
}
The basic idea is that class A provides an implementation of the API defined in APIBase and APIDerived via the call api() which can then act on an object derived from class X and do something different depending on whether it's a Y or a Z.
I would expect this code to give me the following output when run:
foo(Y)
foo(Z)
However, upon compiling this code, gcc gives me the following error:
intf.cpp: In function ‘int main(int, char**)’:
intf.cpp:57:16: error: no matching function for call to ‘APIDerived::foo(Y&)’
a.api().foo(y);
^
intf.cpp:57:16: note: candidate is:
intf.cpp:30:16: note: virtual void APIDerived::foo(Z&)
virtual void foo(Z & z) =0;
^
intf.cpp:30:16: note: no known conversion for argument 1 from ‘Y’ to ‘Z&’
There are two ways I can make this example code compile and give the expected output, but I'm uncertain what differentiates them from the original in the compiler's (or C++ standard's) eyes.
1. Reunite the API by putting both foo() pure function declarations into either APIBase or APIDerived (but not split between them), e.g.:
class APIBase
{
public:
virtual void foo(Y & y) =0;
virtual void foo(Z & z) =0;
};
2. Change class A so that it derives from APIImplementation and ditch the api() redirection calls, e.g.:
class A : public APIImplementation {};
int main(int argc, char * argv[])
{
Y y;
Z z;
A a;
a.foo(y);
a.foo(z);
return 0;
}
I don't want to have to so either of these. Neither do I want to ditch inheritance in favour of templates.
I'm pretty new to C++: please can you help me understand why this example code fails to compile and, if possible, offer workarounds that won't require me to takes steps (1) or (2) above?
Technical details:
Platform: Centos 7, Linux 3.10.0-123.6.3.el7.x86_64
Compiler: gcc (GCC) 4.8.2 20140120 (Red Hat 4.8.2-16)
By default, an f
in the base class and an f
in a derived class are not treated as overloads and overload is not done between them.
The compiler basically walks backwards through scopes to find a scope where there's at least one item with the name it's looking for. Then it looks at everything with that name in that scope, and does overload resolution on them. If there's something at an outer scope from there (e.g., a parent class) with the same name, it will not be included in that overload resolution.
You can, however, bring the inherited name into scope:
struct base {
void foo(int);
};
class derived : public base {
using base::foo;
void foo();
};
Now, if you call foo
in the derived class, the foo
s from the derived class and the base class are treated as an overload set, so the correct one to call will be based on the argument you pass (or lack thereof).