Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there an equivalent of the Java <? extends ClassName> in C++?

I am looking at this https://docs.oracle.com/javase/tutorial/java/generics/subtyping.html and https://docs.oracle.com/javase/tutorial/java/generics/inheritance.html and ask myself how it could be implemented in C++.

I have this small example to illustrate :

#include <iostream>

class Animal
{
public:
    virtual std::string type() const = 0;
    virtual ~Animal() {}
};

class Dog : public Animal
{
public:
    virtual std::string type() const {
        return "I am a dog";
    }
};

class Cat : public Animal
{
public:
    virtual std::string type() const {
        return "I am a cat";
    }
};

template <typename T>
class AnimalFarm
{
};

void farmTest(const AnimalFarm<Animal *> &farm)
{        
    std::cout << "test farm";
}


int main(int argc, char *argv[])
{          
    AnimalFarm<Dog *> dogFarm;
    AnimalFarm<Animal *> animalFarm;

    farmTest(animalFarm); // OK
    farmTest(dogFarm); // NOK compiler error as class AnimalFarm<Dog *> does not inherits from class AnimalFarm<Animal *>

    return 0;
}

I understand why it does not work in C++. In Java, the solution is to use the following construction :

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // OK. List<? extends Integer> is a subtype of List<? extends Number>

(given that Integer is a subclass of Number as pointed out in the example links).

Using :

template <typename U>
void farmTest(const AnimalFarm<U *> &farm);

might be solution but is there a better way to do that in C++ without loosing the fact that Cat or Dog inherits from Animal ( as Integer is a subtype of Number) ?

Thank you.

like image 926
Fryz Avatar asked Dec 26 '17 13:12

Fryz


2 Answers

If your end game is to form an is-a relationship between AnimalFarm<Dog *> and AnimalFarm<Animal *>, then a bit of template specialization with type traits can do that.

template <typename T, typename = void>
class AnimalFarm // Primary template
{
};

template<typename T>
class AnimalFarm<T*,
  std::enable_if_t<
     !std::is_same<T, Animal>::value &&
      std::is_base_of<Animal, T>::value
  >
> // Specialization only instantiated when both conditions hold
  // Otherwise SFINAE
  : public AnimalFarm<Animal*>
{
};

Since AnimalFarm<Animal*> becomes a public base of AnimalFarm<Dog*>, the function parameter reference will bind to it. Though you should note that the corresponding hierarchy is flat, no matter how deep the Animal one goes.

You can check it out live.

like image 152
StoryTeller - Unslander Monica Avatar answered Sep 29 '22 17:09

StoryTeller - Unslander Monica


Equivalence may be a matter of exactly how we define equivalence (if not being opinion).

If you want a runtime open container for any kind of animal...

Sounds like a good opportunity for runtime polymorphism to solve.

A runtime polymorphic solution seems functionally similar if not equivalent (at least in my thinking) to what you're asking for. The best description I know of for the runtime polymorphism that I mean is Sean Parent's "Better Code: Runtime Polymorphism" talk.

Basically though the idea will hide the Animal inheritance hierarchy so from the container's perspective you can just use any container like using AnimalFarm = std::vector<Animal>. The details then go into the implementation of Animal like so:

class Animal {
    struct Concept {
        virtual ~Concept() = default;
        virtual std::string type() const = 0;
        // add in more methods for any other properties your Animal "concept" has
    };

    template <typename T>
    struct Model final: Concept {
        Model(T arg): data{std::move(arg)} {}            
        std::string type() const override {
            return get_type_name(data);
        }
        // override define whatever other methods the concept has

        T data; // stores the underlying type's data
    };

    std::shared_ptr<const Concept> m_self;

public:
    template <typename T>
    Animal(T v): m_self{std::make_shared<Model<T>>(std::move(v))} {}

    // default or delete other constructors as needed.

    friend std::string get_type_name(const Animal& animal) {
        return animal.m_self->type();
    }
};

Now to have an Animal like a Dog, you just need free code like:

struct Dog {
};

inline std::string get_type_name(const Dog& dog) {
    return "Dog";
}

And usage (assuming at least C++11) is like...

using AnimalFarm = std::vector<Animal>;

int main() {
    auto theFarm = AnimalFarm{};
    theFarm.push_back(Dog{});
    for (const auto& e: theFarm) {
        std::cout << get_type_name(e) << "\n";
    }
    return 0;
}

Note that here, Dog is instantiated (Dog{}) and then implicitly gets turned into an Animal because of the way Animal's initializing constructor is defined (both templated and non-explicit).

I just typed this code in by hand. So it's apt to have some mistakes in it. Hopefully it shows the jist of this answer in terms of the code though and I do hope it will help for your need.

If, OTOH, you want the container to only have a specific kind of animal defined by the user of the conatiner...

Then only add instances of the kind of animal to the above styled container.

Or lastly, if you want compiler enforcement...

Then do something like:

template <typename T>
using AnimalFarm = std::vector<T>;

void someCodeBlock() {
    AnimalFarm<Dog> dogFarm;
    dogFarm.push_back(Dog{});
}
like image 44
Louis Langholtz Avatar answered Sep 29 '22 16:09

Louis Langholtz