3

Newbie me is currently stuck at this seemingly simple problem. Let's say I want to write some code about a bunch of animals procreating happily everafter. Obviously, they'd all need a mate() method, so I could define an abstract class like this:

class FemaleAnimal {
public:
    virtual FemaleAnimal mate(const MaleAnimal& a) const = 0;
    virtual MaleAnimal mate(const MaleAnimal& a) const = 0;
}

And derive all the different species:

class FemaleBird : public FemaleAnimal {
public:
    FemaleBird mate (const MaleBird& b) const;
    MaleBird mate (const MaleBird& b) const;
}

class FemaleBee: public FemaleAnimal {
public:
    FemaleBee mate (const MaleBee& b) const;
    MaleBee mate (const MaleBee& b) const;
}

In main(), I want two vectors

males = vector<MaleAnimal>
females = vector<FemaleAnimal>

each of which might contain both birds and bees, and is filled at run time. The species at each index match, so if males[i] is a bee, then females[i] is also a bee, so I can do

vector<FemaleAnimal> femaleOffspring;
vector<MaleAnimal> maleOffspring;
for (int i=0; i<males.size(); ++i){
    femaleOffspring.push_back( females[i].mate(males[i]) );
    maleOffspring.push_back( females[i].mate(males[i]) );
}    

Now, obviously I want the mate() method in the derived classes to implement the one in the base class, but then I'd have to define mate() for animals in general, such as

FemaleBee::mate(const MaleAnimal& a) const;

But bees don't mate with birds. How would I achieve that kind of specialization? Is there some special design pattern for this? I've tried to look into things like covariance, but that was more confusing than helping.

Bonus question: How do I capture the case when males[i] and females[i] are of different species at runtime?

Edit: Assume that males and females come from entirely different class hierarchies and could not sensibly be derived from a common base class.

Edit: For completeness sake, here is the final solution based on n.m.'s answer. Lots of thanks everybody!

#include <iostream>

using namespace std;

class GenericMale {
public:
    virtual void test() = 0;    // just to make the class abstract
};


template <typename Species>
class Male : public GenericMale
{
    void test() {};
};


class GenericFemale {
    virtual void tryMate (const GenericMale&) const = 0;
};


template <typename Species>
class Female : public GenericFemale
{
public:
    virtual void tryMate (const GenericMale& m) const
    {
        try {
            auto& p= dynamic_cast<const Male<Species>&>(m);    // will throw if Species does not match
            Species::doMate(p);
        } catch ( exception& e) {
            cerr << "[MATING ERROR] You filthy animals, stay within your own species!" << endl;
        }
    }
};


class Bee {
public:
    static void doMate(const Male<Bee>& p) {
        cout << "Buzz buzz buzz!" <<endl;
    }
};


class Bird {
public:
    static void doMate(const Male<Bird>& p) {
        cout << "Chirpy chirpy cheep cheep!" << endl;
    }
};



int main() {
    Female<Bee> queenBee;
    Male<Bee> drone;
    queenBee.tryMate(drone);

    Female<Bird> mamaBird;
    Male<Bird> papaBird;
    mamaBird.tryMate(papaBird);

    queenBee.tryMate(papaBird);
}
user32849
  • 609
  • 1
  • 6
  • 16
  • 1
    You're probably looking for the Visitor Pattern. – Quentin Sep 24 '15 at 16:28
  • 1
    You could use possibly use the [curiously recurring template pattern](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). – Some programmer dude Sep 24 '15 at 16:29
  • 2
    First, your vector should be of pointers to the base class (or smart pointers), not objects. – PaulMcKenzie Sep 24 '15 at 16:29
  • 1
    Why downvote/close? This is among the more interesting questions I have read today. – Peter - Reinstate Monica Sep 24 '15 at 16:34
  • 1
    You can't overload on return type, so not sure what you expect `mate()` to do. – Barry Sep 24 '15 at 16:35
  • There's a very broad variety of design patterns available to solve that private. For such simple case, I'd go with a simple class attribute like `enum Gender { Male, Female };` – πάντα ῥεῖ Sep 24 '15 at 16:35
  • 1
    You can certainly make Animals abstract classes: The concrete derivations must all be able to mate, but the base does not know how. I'm also not quite sure whether we need gender as a type; I would probably make it an attribute, for example allowing later extension to neutrals. – Peter - Reinstate Monica Sep 24 '15 at 16:37
  • @PeterSchneider Interesting may be, but far too broad to fit Stack Overflows desired Q&A format IMHO. – πάντα ῥεῖ Sep 24 '15 at 16:38
  • This example is somewhat artificial in that you could sensibly derive both male and female from an Animal class. Let's keep this more general and assume males and females are vastly different classes. – user32849 Sep 24 '15 at 16:39
  • 1
    @PaulMcKenzie's remark is very important. Polymorphism works only with pointers or references. If you have a vector of Animals and assign derived classes to its elements you "slice" the objects, stripping them of the derived attributes and features. Your compiler would actually forbid that if the base was abstract. – Peter - Reinstate Monica Sep 24 '15 at 16:40
  • 3
    @πάνταῥεῖ Hm. There is concrete code, a specific question, a problem which I have probably encountered. – Peter - Reinstate Monica Sep 24 '15 at 16:41
  • dynamic cast could be one way to check whether a base class pointer actually matches a specific class. – Support Ukraine Sep 24 '15 at 16:44
  • @PeterSchneider Well, write a concise answer then. Please cover **all aspects**, and possible solutions there please. I'll certainly upvote it then. – πάντα ῥεῖ Sep 24 '15 at 16:44
  • 2
    Take a look at the [double disptach](https://en.wikipedia.org/wiki/Double_dispatch) pattern – Buddy Sep 24 '15 at 16:46
  • 2
    The only thing I would change about this question is the title. "Birds, bees and abstract classes" isn't really clear. It attracts attention, true. But this is not a newspaper. The title should describe the question, not attract. I would choose something like "How to distinguish different subclasses derived from the same abstract class?". It can certainly be further improved, but it would still be better than talking about bees and birds. – Fabio says Reinstate Monica Sep 24 '15 at 16:54
  • It's not a simple problem, far from it. – n. m. could be an AI Sep 24 '15 at 16:58
  • 1
    @πάνταῥεῖ "All aspects?" Emphasized? I'm not the Dalai Lama ;-). – Peter - Reinstate Monica Sep 24 '15 at 17:37
  • @PeterSchneider Sure you're not. And that might be even too broad for giving a concise answer for his holiness (he's pretty good in giving koans though, I would accept such equally ;-) ). – πάντα ῥεῖ Sep 24 '15 at 17:41
  • 1
    _@user32849_ _"Assume that males and females come from entirely different class hierarchies and could not sensibly be derived from a common base class."_ That's a good argument for not introducing one. As already mentioned, template properties along CRTP could point to a good solution , to match correct combinations at compile time. What you actually want, seems to be a [Policy based design](http://stackoverflow.com/questions/872675/policy-based-design-and-best-practices-c). – πάντα ῥεῖ Sep 24 '15 at 17:56
  • Just to clarify, males and females each do have their hierarchy, but there is no common Animal class. I guess Hierarchies of jobs and workers would have been a better example. – user32849 Sep 24 '15 at 17:58

3 Answers3

3

You basically have two options: compile-time checks and run-time checks.

Compile-time

Your MaleAnimal and FemaleAnimal objects don't have mate methods with the signature you gave. Indeed, since any female cannot mate any male, the method signature

FemaleAnimal::mate(const MaleAnimal& a) const

is an overly generous promise. Don't promise what you cannot fulfil.

A FemaleAnimal should be able to mate with a MaleAnimal of the same species. How do we write that down? We encode the species in the type of the animal. In C++, that would be a template.

template <typename Species>
class Animal ...

template <typename Species>
class MaleAnimal : public Animal<Species> ...

template <typename Species>
class FemaleAnimal : public Animal<Species>
{ ...
   void mate (const MaleAnimal<Species>&) const ...
}

This type-safety comes with a price. You cannot keep representatives of different species in the same container and retain their species information. You need a separate container of a distinct type for each species.

 class AnimalContainer
 { ...
    virtual void mateAll() = 0;
 };

 template <typename Species>
 class SpecificContainer : public Container
 {
    ...
    std::vector<MaleAnimal<Species>> males;
    std::vector<FemaleAnimal<Species>> females;
    void mateAll()
    {
       for (...) females[i].mate(males[i]);
    }        
 }

Now you can keep a container of AnimalContainers and each one can keep a separate species.

Run-time

Your FemaleAnimal can try to mate any MaleAnimal. The attempt could fail. This is best modelled with dynamic_cast: if the check passes, it returns the right type, and if not, it fails (throws an exception).

The hierarchy can be modified thus:

class Animal ...

class GenericMaleAnimal : public Animal ...
class GenericFemaleAnimal : public Animal 
{ ...
   virtual void tryMate (const GenericMaleAnimal&) const = 0;
};

template <typename Species>
class MaleAnimal : public GenericMaleAnimal ...

template <typename Species>
class FemaleAnimal : public GenericFemaleAnimal
{ ...
   virtual void tryMate (const GenericMaleAnimal& m) const
   {
      // will throw if species don't match
      auto& other = dynamic_cast<const MaleAnimal<Species>&>(m);
      // Specific mate procedure for given species
      Species::doMate(*this, other);
   }
};

Now you can keep everything in the same container, and it's your responsibility to make types match.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • I think the second one is what I'm looking for, though I'm a little concerned about performance of dynamic_cast for large animal ecosystems, but that's a different topic I guess ;-) Thanks! – user32849 Sep 24 '15 at 21:10
2

Your description might point to the answer in that:

The species at each index match, so if males[i] is a bee, then females[i] is also a bee

covers the specialization

But bees don't mate with birds. How would I achieve that kind of specialization?

Q: How did you guarantee they match up? A: You must have known the types of both entries at the same time! And when you did, simply set up a command pattern, lambda, etc. to call the specialized method with the two known types.

Finally look at this use of enable_if -- could be used to satisfy your problem if I am incorrect regarding the above statement.

Graeme Wicksted
  • 1,770
  • 1
  • 18
  • 21
1

"How do I capture the case when males[i] and females[i] are of different species at runtime?": Depends whether the attempt to mate the wrong one is an error or not. If it's ok to try but just doesn't work, you could perform some check in the mate() function, e.g. StillLearning's suggestion of a dynamic cast of the argument (is it my species?). If you want to be more flexible you could make that test configurable with a check functor that can be provided e.g. in the constructor of an animal, or as an optional argument to mate(). If the test is negative simply do nothing.

If a bad mating attempt is an error (because you set your vectors up badly and cannot continue) you can just throw an exception.

Edit: @Buddy's pointer to the double dispatch pattern is useful and elegant for mating differently with different species (different subclasses of their respective bases), even though just a pointer or reference to a base is provided. The double dispatch could implicitly incorporate a "check" by overloading mate_callback() (or whatever -- the function implemented in the original caller which is called by the original argument) for desired species sensibly and having a catch-all that throws or does nothing for all other species. Double dispatch also pleases me ethically because it requires mutual consensus when mating.

Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
  • I don't quite understand - the base classes are already abstract, aren't they? In fact, FemaleAnimal::mate() is pure virtual. – user32849 Sep 24 '15 at 17:08
  • Right, I missed that... after all you had vectors `vector`. (That also obviates the hint wrt to pointers instead of objects in the vector.) I see now. I misunderstood "then I'd have to define mate() for animals in general". The general was refering to the argument! Then indeed the double dispatch is your friend. Read the link @Buddy provided. – Peter - Reinstate Monica Sep 24 '15 at 17:17