Home > Net >  Override C method from multiply inherited templated base class
Override C method from multiply inherited templated base class

Time:02-22

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.

  • Related