The enclosed code is a trimmed-down example of something I'm encountering.
First, here's an even more simplified version that manages to compile:
#include <string>
template<typename T>
class Base
{
public:
Base() {}
virtual ~Base() {}
virtual size_t Size (void) const = 0;
};
template<typename T>
class Derived : public virtual Base<T>
{
public:
Derived() {}
virtual ~Derived() {}
virtual size_t Size (void) const override { return 0; }
};
class Impl : public virtual Derived<std::string>,
public virtual Base<char const *>
{
public:
Impl() {}
virtual ~Impl() {}
using Base<char const *>::Size;
virtual size_t Size() const override { return 1; }
};
int
main(int argc, char **argv)
{
Impl oImpl;
}
The issue is being able to implement Base<std::string>::Size()
as well as Base<char const *>::Size()
.
This question was instrumental in reaching this stage. (The using
statement was key.)
However, the problem is that, in the real code, I'm not merely returning a size_t
, but an instance of a nested class.
#include <string>
template<typename T>
class Base
{
public:
Base() {}
virtual ~Base() {}
class Contained
{
public:
Contained();
virtual ~Contained();
};
virtual Contained begin (void) const = 0;
};
template<typename T>
class Derived : public virtual Base<T>
{
public:
Derived() {}
virtual ~Derived() {}
virtual typename Base<T>::Contained begin (void) const override
{ return typename Base<T>::Contained(); }
};
class Impl : public virtual Derived<std::string>,
public virtual Base<char const *>
{
public:
Impl() {}
virtual ~Impl() {}
using Base<char const *>::begin;
virtual typename Base<char const *>::Contained begin() const override
{ return typename Base<char const *>::Contained(); }
/* virtual typename Base<char const *>::Contained Base<char const *>::begin() const override
{ return typename Base<char const *>::Contained(); } */
};
int
main(int argc, char **argv)
{
Impl oImpl;
}
Somehow, g /clang thinks this is a redefinition of Base<std::string>::Size
, not Base<char const *>::Size
– it complains of an invalid covariant type. So the using
statement that worked so well in the first code sample is ineffective here.
If I try to fully qualify the name, i.e. the commented-out section, g gives me cannot define member function 'Base<const char*>::Size' within 'Impl'
(without saying why), and clang gives me non-friend class member 'Size' cannot have a qualified name
(which I don't understand at all).
Does anyone know how to override methods from different instances of the same templated base class?
If anyone's interested, Base
is a generic "enumerable" class, and I'm trying to create a class that has both a mutable interface (i.e. derived from Base<std::string>
) and a read-only interface (i.e. derived from Base<char const *>
) that doesn't leak how the string data is stored internally. (In general, I'm trying to implement C#-style enumerable interfaces in C , e.g. so a client doesn't have to know that the underlying class is based on a list, vector, set, map, or whatever, only that its contained values can be enumerated through a non-specific interface class...implying that Contained
is actually an iterator class).
CodePudding user response:
What you have is essentially this:
struct A {
virtual int foo() { return 0;}
};
struct B {
virtual double foo() { return 0;}
};
struct C : A, B {
int foo() override { return 42; }
double foo() override { return 3.14; }
};
Oops! You can't do that. Each overrider of foo() in C overrides all foo() (same argument list, any return type) in all base classes. No fiddling with using
changes that.
What you need is essentially rename foo such that C inherits two functions of different names. Stroustrup proposes a technique of doing exactly that (in D&E I think but don't quote me on that).
struct A {
virtual int foo() { return 0;}
};
struct B {
virtual double foo() { return 0;}
};
struct AA : A {
virtual int foo1() { return 1; }
// Essentially rename foo to foo1
int foo() override { return foo1(); }
};
struct BB : B {
virtual double foo2() { return 1; }
// Essentially rename foo to foo2
double foo() override { return foo2(); }
};
struct C : AA, BB {
int foo1() override { return 42; }
double foo2() override { return 3.14; }
};
Now C still has two functions named foo()
but they are frozen. From this point on, you manipulate only foo1 and foo2. Of course you can still call foo() via A and B, and these will call the correct version.
So yes, C::foo is essentially a forbidden name. That's the price.