3

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");

}
Dave
  • 2,653
  • 4
  • 20
  • 22
  • πάντα ῥεῖ, good point! I forgot that I could just edit the question! :) Fixed. – Dave Nov 06 '15 at 20:57
  • Your virtual function isn't pure. Check out if this gives you a reasonable solution: http://stackoverflow.com/questions/2919584/override-number-of-parameters-of-pure-virtual-functions – o_weisman Nov 06 '15 at 21:05
  • 1
    @o_weisman, Thanks for your comment. Making my Base function a pure virtual doesn't seem to solve this design problem. The question you referenced was good food for thought, but I can't figure out how I'd apply it without proliferating specific subclasses for each of the Derived types I might use. In other words without an instance-choosing mechanism built into the template definition, lots of specific classes have to be written, and instantiated. Thank you nonetheless, I appreciate your helpfulness. – Dave Nov 06 '15 at 22:34
  • 1
    Doing some searching I think what you want is function templates, here's an example http://stackoverflow.com/a/972170/2899478 – o_weisman Nov 07 '15 at 09:30
  • @o_weisman Thank you! I was able to come up with a solution using function templates... I posted an answer based on them that functions correctly. Thanks for steering me in a more productive direction! – Dave Nov 08 '15 at 19:51

5 Answers5

3

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; }
    };
    
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Good call with the `override` keyword. – GreatAndPowerfulOz Nov 06 '15 at 20:48
  • Thanks for your thoughts R Shau. 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. – Dave Nov 06 '15 at 20:51
  • @RSahu... Thanks for the edit... oh, I wish that worked for me! It seems like such an elegant and simple change, but unfortunately it produces the same results for me. – Dave Nov 06 '15 at 21:02
  • @NathanOliver, I don't think you meant to ask what that question indicates. I think you meant to ask a different question whose answer is: No, you won't be able to invoke the `Derived` member function from the pointer. – R Sahu Nov 06 '15 at 21:02
  • @RSahu No. Sorry about that. I should probably log off and get some rest. thanks though. – NathanOliver Nov 06 '15 at 21:04
  • @Dave, unfortunately, the overload works only when you use the derived class pointer. Hope the answers and the comments give you enough material to devise a solution that works for you. – R Sahu Nov 06 '15 at 21:10
  • Thanks for your help R Sahu, its definitely food for thought, although I haven't gotten anything working yet. – Dave Nov 06 '15 at 21:15
1

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

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Thanks for your thought Nathan. 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. – Dave Nov 06 '15 at 20:50
  • @Dave Doing that will break the virtual mechanism as you have seen. If the function is a overload then when you call the function from a `Base*` then only the `Base*` function will be considered. – NathanOliver Nov 06 '15 at 20:51
  • Nathan, yeah.. its definitely broken! :) Can you think of any other way I might pick the derived template class function from the type of its argument? – Dave Nov 06 '15 at 21:17
  • @Dave I don't know of a way other than making it a `void*` and then casting to the template type in the function. You might want to look at `boost::variant` or `boost::any` – NathanOliver Nov 06 '15 at 21:22
  • I'll have a look at those. Any idea if I could put a function template in Base for match, and then specialize it by the same type that Derived uses in the Derived class? – Dave Nov 06 '15 at 21:27
1

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.

GreatAndPowerfulOz
  • 1,767
  • 13
  • 19
  • Thanks for your answer Gread.And.Powerful.Oz! I agree that would work, but it would also require me to modify the Base class with a specific function overload for every type I ever intended to use with the system. This is what I'm hoping to avoid. Can you think of another mechanism that would accomplish this, if this one can't? – Dave Nov 06 '15 at 20:54
  • @Dave The only thing I can think to do is have the `bool Derived::match(const Type* data) { return match((void*)data); }` function call another `virtual bool Derived::match(const void* data) override { return true; }`. The first function is an overload, the second is the override. Or, call the other way around. – GreatAndPowerfulOz Nov 06 '15 at 21:35
  • Thanks again. Unf. when I do that, I get false positives: Derived( )->match(&i) returns true, when I want it to return false (because &i is an int* not a float* ). – Dave Nov 06 '15 at 21:45
  • @Dave, you'd need to make the `match(void *)` function in `Derived` private to avoid that. – GreatAndPowerfulOz Nov 06 '15 at 22:30
  • Oh sneaky! :) That modification does indeed work for B and D, but fails for the counter-example E (see modified question above), I get the following compiler error 'match' is a private member of 'Derived.' I guess with match( const void *) private, there is no more 'catch all' function to return false for non-matching types. Thank you nonetheless! I appreciate your creativity. – Dave Nov 06 '15 at 22:57
  • What is it you're trying to do? Some sort of runtime variable type matching? – GreatAndPowerfulOz Nov 06 '15 at 23:21
  • It is a lightweight RTTI system, used for auto-generating / parsing json files of the types that have been described to the system. I'm trying to perfect this mechanism so I can just say to the system TheSystem::save( &anInstance ) and it'll deduce and call the appropriate type-specific stuff. – Dave Nov 07 '15 at 00:32
1

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.

Chris Hayden
  • 1,104
  • 6
  • 6
  • Thanks for your answer. I had a look at covariant return types -- interesting. I can't think of how I'd apply it in the above situation though, as I'm trying to pick a template class method based on the type of the argument(s) to a member function. Good idea though, and I learned something new! :) – Dave Nov 06 '15 at 21:49
  • 1
    Yea, sorry, I wasn't trying to imply covariant return types solved this particular problem. Should have been more clear about that. I was worried that if I didn't mention it someone might object to my response on that (superfluous) ground. – Chris Hayden Nov 07 '15 at 18:12
1

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
Dave
  • 2,653
  • 4
  • 20
  • 22