2

What I want to do expose a name of member function to tell other developers that every derived class has to implement the specific function with the same name exposed in the interface, but the implemented function can use different signatures.

To test the interface with gtest, I have to keep the virtual functions in the example:

class Fruit {};
class Meat {};
class Finger {};

class Animal {
 public:
  virtual void eat(/* something */) = 0;
};

class Monkey : public Animal {
 public:
  void eat(Fruit fruit, Finger finger) override {
    std::cout << "I eat fruits and suck my fingers" << std::endl;
  }
};

class Dog : public Animal {
 public:
  void eat(Meat meat) override {
    std::cout << "I eat meat" << std::endl;
  }
};

int main() {
  Meat meat;
  Fruit fruit;
  Finger finger;
  Monkey monkey;
  Dog dog;
  monkey.eat(fruit, finger);
  dog.eat(meat);
}

After reading:

I know there are two possible solution, one is using dispatcher, and the other is using CRTP.

Dispatcher: This solution breaks the type check.

class EatDispatcherInterface {
 public:
  virtual void eat(Monkey* monkey) = 0;
  virtual void eat(Dog* dog) = 0;
};

class EatDispatcher : public EatDispatcherInterface {
 public:
  EatDispatcher(Fruit fruit) : food(fruit) {}
  EatDispatcher(Meat meat) : food(meat) {}
  void eat(Monkey* monkey) override {
    std::cout << "I eat fruits" << std::endl;
  }
  void eat(Dog* dog) override {
    std::cout << "I eat meat" << std::endl;
  }
 private:
  union {
    Fruit fruit;
    Meat meat;
  } food;
};

class Fruit {};
class Meat {};

class Animal {
 public:
  virtual void accept(const EatDispatcher& eat_dispatcher) = 0;
};

class Monkey : public Animal {
 public:
  void eat(const EatDispatcher& eat_dispatcher) override {
    eat_dispatcher.eat(this);
  }
};

class Dog : public Animal {
 public:
  void eat(const EatDispatcher& eat_dispatcher) override {
    eat_dispatcher.eat(this);
  }
};

int main() {
  EatDispatcher fruit_eat_dispatcher(Fruit());
  EatDispatcher meat_eat_dispatcher(Meat());
  Monkey monkey;
  Dog dog;
  monkey.accept(fruit_eat_dispatcher);
  dog.accept(meat_eat_dispatcher);
}

CRTP: This solution exposes the detail of the derived class in the interface.

class Fruit {};
class Meat {};

template <typename Food>
class Animal {
 public:
  virtual void eat(Food food) = 0;
 };

class Monkey : public Animal<Fruit> {
 public:
  void eat(Fruit fruit) {
    std::cout << "I eat fruits" << std::endl;
  }
};

class Dog : public Animal<Meat> {
 public:
  void eat(Meat meat) {
    std::cout << "I eat meat" << std::endl;
  }
};

int main() {
  Monkey monkey;
  Dog dog;
  monkey.eat(Fruit());
  dog.eat(Meat());
}

Which is the proper pattern? Is there other solutions without these disadvantage?

Copyright 2021 Google LLC.

SPDX-License-Identifier: Apache-2.0

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
IvanaGyro
  • 598
  • 7
  • 25
  • 1
    The [`override`](https://en.cppreference.com/w/cpp/language/override) keyword is just a way to tell the compiler that the signatures should match 100% with the virtual function in the base class, remove it and it will work [just fine](https://godbolt.org/z/T7a1ea7Mj). – Ruks Aug 13 '21 at 14:18
  • @Ruks it wont be overriding the base class any more though, it'll be a new function with a different signature – Alan Birtles Aug 13 '21 at 14:25
  • When your base class is a template class it is no longer usable as a base class. The template makes it unique for each type of food. – Bart Aug 13 '21 at 14:30
  • I don't see any use of polymorphism and therefore need for virtual functions in your sample? – Alan Birtles Aug 13 '21 at 15:17
  • "What I want to do expose a name of member function to tell other developers that every derived class has to implement the specific function with the same name exposed in the interface, but the implemented function can use different signatures." Is this just documentation? How does it help to know an `eat` member function exists if you don't know how to call it? – Jeff Garrett Aug 13 '21 at 15:41
  • What if the _parameter_ to the virtual function was _also_ a type in a separate class hierarchy. The function would become `void eat(const Food&)` and the _type_ of food would be polymorphic as well. – Chad Aug 13 '21 at 16:33
  • Just one point, "Dispatcher: This solution breaks the type check" if not, the compiler must know how function is defined in child class to check params, which makes the function define no longer "general", and should not exists in interface class. So it MUST breaks the type check to satisfy this usage, (no matter whether use dispatcher or maybe va_list). Or else, maybe you just hope 3 different function have same name? (As I know, c++ cannot extract defines from child and combine it to parent at compile time, so there is no third way) – wwc Aug 13 '21 at 18:49
  • @Alan Birtles I need to use gtest to test the interface. – IvanaGyro Aug 13 '21 at 19:33

3 Answers3

1

There are a few things you can do, although I don't know if I care for any of these.

I don't like either the dispatcher nor depending upon exposing the details of derived classes. But...

If the list of things that can be eaten is small and controlled, you can implement an eat() method for each one in the base class.

If the list is long or could be dynamic, then you could decide they must all derive from Food, and you could also have a field in Food that is the type of food. Then the eat method can take a Food, and your subclasses check that the type is something that animal recognizes as food. This is probably what I would do.

Or you might be able to do something clever with a templated method. I'm not quite sure what, as your example is contrived.

Joseph Larson
  • 8,530
  • 1
  • 19
  • 36
1

The question here is what do we even by "a virtual function with different signatures"?

Strictly speaking what you want is impossible: in C++ if a function overrides a member function of an ancestor class then the two functions have the same signature by definition. So what you want is some code that behaves as though it implements a virtual function with multiple signatures.

Okay, so what does that mean? Well it depends on what you want to use the code for. For example, one big use of virtual functions is making polymorphic calls through pointers to a base class without having to know the concrete class of the objects the pointers point to. In your code in the question, the Dispatcher solution will let you do this. It solves that problem if that is the problem you have.

The CRTP version does not solve that problem. Monkey and dog do not share a base class -- one inherits from Animal<Fruit> and the other from Animal<Meat> so you could not make polymorphic eat calls to an animal because you can not have a pointer to an abstract animal. The CRTP version, however, would allow you to make polymorphic calls through an opaque pointer to a carnivore or an opaque pointer to an herbivore, if that was something you needed to do, and the Dispatcher version would not.

The point I am making is that since you are asking for something that is syntactically and semantically impossible in the language the question is ill-formed in that the answer depends on exactly what it is you are trying to accomplish which is external to the question as posed.

jwezorek
  • 8,592
  • 1
  • 29
  • 46
1

What I want to do expose a name of member function to tell other developers that every derived class has to implement the specific function with the same name exposed in the interface, but the implemented function can use different signatures.

There is no way to do that. It makes no sense. Even if you could do that, it would be completely useless.

A virtual function has to have the same signature throughout the hierarchy for a reason. You can call that function without knowing the exact type of the object. The signature is a contract. The contract is enforced throughout the hierarchy so that the users can rely on it. If a user has a pointer to Base, they can call any virtual method on it, regardless of the dynamic type of the object.

There is no reason in the world for the developer of Animal to force the developer of Monkey to implement eat with whatever signature they like. This method would not be callable from a pointer of reference to Animal, so it's not the business of Animal developers to dictate its existence for classes that are not Animal.

But I want Monkey::eat to be callable using an Animal pointer or reference!

If you know you have a Monkey and a Fruit, just use correct types for your variables. Not Animal* theAnimal, but Monkey* theAnimal. No need to call eat from Animal.

If you know you have some animal and some food compatible with that animal, but don't know what exactly, you better express souch knowledge in the types of your variables. Something like AnimalAndFood abstract base class could be useful, with derived classes like MonkeyAndFruit and DogAndMeat. There is no MonkeyAndMeat, so there is no risk of food mismatch. template <class Food> class Animal falls in the same general category.

If you know you have some animal and some food but you are not sure about their compatibility, you need to do a run-time check.

Bit I don't like any of these solutions!

That's about all the tools we have.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243