简体   繁体   中英

How can I partially overload a virtual function in a C++ template subclass?

I'm trying to create a non-template base class to create an interface through which I can interact with derived template classes. My hope is to use partial virtual function overloads, but something isn't working, and I'm not sure why. Can anyone explain why I the below code returns:

B match = False
D match = True
E match = False

instead of B,D returning True, and E returning False? I would have thought that the overloaded operator in the derived class would pick up the pointer-to-int '&i' and be called, but it doesn't.

To be clear, I'm not trying to -override- the Base version of match, I am trying to -overload- it, specifically wanting it to have a different, in this case more specialized, interface than the one in Base, which takes over when its function signature is used.

I'm also trying to avoid having to extend the Base class for every flavor of the Derived template that I might instantiate.

Even stranger, and I -might- just be going crazy here, but I swear this was working for me at some point not too long ago! Fwiw I'm on OSX 10.5, using llvm 7.0.0 and clang 700.1.76. Could this be a compiler idiosyncracy?

While I am attempting (unsuccessfully) to use partial overloads here, I'm really open to any approach that solves the problem of picking a template instance function by the type of its arguments, without proliferating classes, if-thens/case or adding specific specializations to the Base class. I'd love to hear it if you have another approach that I could drop in for similar functionality.

Thank you for any insights you can offer!

#include <stdio.h>

class Base
{
public:
  Base() {}
  virtual ~Base(){}

  virtual bool match( const void *data ) const { return false; }
};

template <class Type>
class Derived: public Base
{
public:
  Derived():Base() {}
  ~Derived() override{}

  virtual bool match( const Type *data ) const { return true; }
};

int main(int argc, char **argv)
{
  Derived<int>   *d = new Derived<int>();
  Derived<float> *e = new Derived<float>();
  Base *b           = d;

  int i;
  printf("B match = %s\n",b->match(&i)?"True":"False");
  printf("D match = %s\n",d->match(&i)?"True":"False");
  printf("E match = %s\n",e->match(&i)?"True":"False");

}

If you were to manually create Derived<int> as a class, its member function would be:

virtual bool match( const int *data ) const { return true; }

It doesn't override the base class. Hence, when you invoke the function with base class pointer, it executes the base class implementation and when you invoke it with the derived class pointer, it executes the derived class implementation.

You will be able to catch the problem at compile time if you use the override keyword.

template <class Type>
class Derived: public Base
{
public:
  Derived():Base() {}
  ~Derived() override{}

  virtual bool match( const Type *data ) const override { return true; }
};

You should see a compile time error with that change.

See the compiler error at http://ideone.com/8rBQ6B .

Update, in response to OP's comment

If you don't mean to override:

  1. Don't use virtual in the member function declaration.
  2. Bring the base class function into the scope of the derived class by using

     using Base::match 

    Here's how to do it:

     template <class Type> class Derived: public Base { public: Derived():Base() {} ~Derived() override{} using Base::match; bool match( const Type *data ) const { return true; } }; 

The problem here is that Derived::match does not match Base::match . The function signatures of virtual functions have to be the same for the mechanism to work. So with your code Derived::match overloads instead of overriding it.

If we change

virtual bool match( const Type *data ) const { return true; }

To

virtual bool match( const void *data ) const { return true; }

Then we get

B match = True
D match = True

Live Example

because the signatures of the functions do not match:

In class Base you have

virtual bool match( const void *data ) const { return false; }

note the const void * parameter type

then in Derived you have

virtual bool match( const Type *data ) const { return true; }

Where Type in this instance is int (from main 's Derived<int> *d )

Just add a virtual in Base with a const int* data signature, then you'll be good to go.

And yes, that does mean that in Base you'll have to add an overload of match for every Type you expect to use with Derived .

The reason this doesn't work is because the derived class doesn't actually override the virtual function in the base class. In order to override a virtual function in a base class a derived class must have exactly the same signature (see note below for an exception). In this case the signatures are different because the virtual function in the base class takes a void * but the function in the derived class takes a type * .

Let's make one small change and add the override directive to the function signature in the derived class:

#include <stdio.h>

class Base
{
public:
  Base() {}
  virtual ~Base(){}

  virtual bool match( const void *data ) const { return false; }
};

template <class Type>
class Derived: public Base
{
public:
  Derived():Base() {}
  ~Derived() override{}

  virtual bool match( const Type *data ) const override { return true; }
};

int main(int argc, char **argv)
{
  Derived<int> *d = new Derived<int>();
  Base *b         = d;

  int i;
  printf("B match = %s\n",b->match(&i)?"True":"False");
  printf("D match = %s\n",d->match(&i)?"True":"False");

}

Here's what happens when we compile now:

main.cpp: In instantiation of 'class Derived<int>':
main.cpp:24:38:   required from here
main.cpp:19:16: error: 'bool Derived<Type>::match(const Type*) const [with Type = int]' marked 'override', but does not override
   virtual bool match( const Type *data ) const override { return true; }

Assuming we're using C++11 it's always a good idea to use override to be sure we really are overriding a base class virtual function.

Note from above: There's something called a covariant return type where an override doesn't have to have the same signature, but that's beyond the scope of the question.

Thanks to o_weisman 's comment under the original question, I was able to find a working solution using function templates, (see below). Admittedly in my solution's current form, I leverage a restriction that each Type-instance of Derived is a singleton. This works well for my particular design, but the behavior could be expanded as needed. One possibility that would allow for multiple instances of Derived might be to check if the 'this' pointer in Base::match is in a set of all instances (tracked in a static set variable updated at construct/destruct time), instead of against a single singleton instance. Anyhow, I hope this helps someone who might be facing a similar design challenge.

#include <stdio.h>
#include <assert.h>

template <class Type> class Derived;
class Base
{
public:
  Base() {}
  virtual ~Base(){}

  template <class Type>
  bool match( const Type *data ) const { return (Derived<Type>::instance() == this); }

};

template <class Type>
class Derived: public Base
{
public:
  Derived(): Base() { assert(!ourInstance); ourInstance = this; }
  ~Derived() override{}

  static const Base *instance() { return ourInstance; }

protected:
  static const Derived<Type> *ourInstance;

};

template <class Type>
const Derived<Type> *Derived<Type>::ourInstance = NULL;

int main(int argc, char **argv)
{
  Derived<int>   *d = new Derived<int>();
  Derived<float> *e = new Derived<float>();
  Base *b           = d;

  int i;
  printf("B match = %s\n",b->match(&i)?"True":"False");
  printf("D match = %s\n",d->match(&i)?"True":"False");
  printf("E match = %s\n",e->match(&i)?"True":"False");

}

This produces the desired result of:

B match = True
D match = True
E match = False

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM