4

Given a class that have some enum that defines a type of the class, like in following example:

class Fruit {
 public:

   enum class FruitType {
      AppleType = 0,
      OrangeType = 1,
      BananaType = 2,
   };
   Fruit(FruitType type) : type_(type) {}
   FruitType fruit_type() const { return type_; }

 private:
   FruitType type_;
};

and a class derived from it that shares the same enum:

class DriedFruit : public Fruit {
 public:
  // Some Dried specific methods.
};

Would it be possible to somehow define a distinct types for Fruit and DriedFruit with each of the specific enum values:

class Apple   // Fruit with FruitType = AppleType
class Orange  // Fruit with FruitType = OrangeType
class Banana  // Fruit with FruitType = BananaType
class DriedApple   // DriedFruit with FruitType = AppleType
class DriedOrange  // DriedFruit with FruitType = OrangeType
class DriedBanana  // DriedFruit with FruitType = BananaType

so that 3 classes Apple, Orange and Banana are distinct types, and 3 classes DriedApple, DriedOrange, DriedBanana are distinct types.

My question is somewhat similar to How to define different types for the same class in C++, except that I want to explicitly store information about class type as enum member variable in the class, and to have a common base class for all distinct types.

What would be the most efficient way to do that?

EDIT: The main use case is as follows - in my applications, there are certain methods that only expect Apple as an input, or only expect Orange as an input, and many methods that do not care which fruit it is.

It feels unsafe/obscure to pass Fruit to method that only expects Apple, at the same time there are many methods that do not care which type it is so having 3 distinct types is not a good option either.

The main workflow is as follows: build a Fruit from some input parameters, then pass it around and process it as a Fruit, then at some point if it is an Apple, convert from Fruit to concrete Apple type, and further process it, restricting it type to an Apple from that point onward.

Ilya Kobelevskiy
  • 5,245
  • 4
  • 24
  • 41
  • Doesn't the answers to the question you link answer this question? – François Andrieux Nov 29 '17 at 19:42
  • 1
    Anything wrong with making `type_` protected, and having each derived class set it appropriately in the constructor?? – zzxyz Nov 29 '17 at 19:48
  • 1
    This looks a bit of a XY-problem. Why do you really need to have _distinct types_ for specific instances of a `Fruit`? Specialized interfaces implemented in the common class? The latter would smell as a design flaw. – user0042 Nov 29 '17 at 19:57
  • @user0042 - it may well be XY-problem - I've expanded my question to hint about use case – Ilya Kobelevskiy Nov 29 '17 at 20:01
  • @Ilya Kobelevskiy I think you need to expand on this bit `then at some point if it is an Apple, further process it, restricting it type to an Apple from that point onwards.` How would you like that to look like in code? Because I think that might be your "X" and this enum is your "Y". – Chris Drew Nov 29 '17 at 21:03
  • @ChrisDrew What about _empty_ tag interfaces? A `struct` with nothing else than a `virtual` destructor, that can be queried with a dynamic cast. – user0042 Nov 29 '17 at 21:10
  • @ChrisDrew I updated question, once necessary, Fruit will be converted to Apple and will remain Apple from that point onward. – Ilya Kobelevskiy Nov 29 '17 at 21:40

3 Answers3

3

What would be the most efficient way to do that?

You can make use of non-type template parameters:

enum class FruitType {
   AppleType = 0,
   OrangeType = 1,
   BananaType = 2,
};

template <FruitType F>
class Fruit {
 public:
   FruitType fruit_type() const { return F; }
};

using Apple = Fruit<FruitType::AppleType>;
using Banana = Fruit<FruitType::BananaType>;

Whether or not you need an actual base class is up to you. It may be also sufficient to provide template specializations for certain FruitTypes.

Jodocus
  • 7,493
  • 1
  • 29
  • 45
2

Is this what you would like to do?

enum class FruitType 
{
    AppleType = 0,
    OrangeType = 1,
    BananaType = 2,
};

class Fruit 
{
public:

    virtual FruitType fruit_type() const = 0;
};

class Apple: public Fruit 
{
public:

    FruitType fruit_type() const override { return FruitType::AppleType; }
};

class Orange : public Fruit 
{
public:

    FruitType fruit_type() const override { return FruitType::OrangeType; }
};

class Banana : public Fruit 
{
public:

    FruitType fruit_type() const override { return FruitType::BananaType; }
};

int main()
{
    Fruit *somefruit = new Apple;

    std::cout << "Is Apple? " << std::boolalpha << (somefruit->fruit_type() == FruitType::AppleType) << std::endl;
    std::cout << "Is Orange? " << std::boolalpha << (somefruit->fruit_type() == FruitType::OrangeType) << std::endl;
    std::cout << "Is Banana? " << std::boolalpha << (somefruit->fruit_type() == FruitType::BananaType) << std::endl;

    return 0;
}

Prints:

Is Apple? true
Is Orange? false
Is Banana? false
Killzone Kid
  • 6,171
  • 3
  • 17
  • 37
  • +1. Thanks, that would work, but the problem is that Fruit is already a base class in my application, so that approach would lead to multiple inheritance which I would like to avoid. – Ilya Kobelevskiy Nov 29 '17 at 20:06
  • 3
    @IlyaKobelevskiy I don't see how this approach could lead to multiple inheritance. Can you share an example? In any case, if it's true for this solution, it seems like it would be true for any conceivable solution. Though maybe I didn't understand what you meant. To be clear, A inheriting from B and B inheriting from C is not multiple inheritance and is not usually a problem. – François Andrieux Nov 29 '17 at 20:08
  • @FrançoisAndrieux The problem is that in my application there is also DriedFruit, which is derived from Fruit on concept orthogonal from FruitType. I need to process objects of type Fruit and DriedFruit, where DriedFruit is Fruit plus something else. With this solution Fruit becomes abstract, so DriedFruit becomes abstract as well - we now need to define DriedApple, DriedOrange and DriedBanana to be able to instantiate concrete instances of DriedFruit. – Ilya Kobelevskiy Nov 29 '17 at 20:49
  • @IlyaKobelevskiy Since it seems `DriedFruit` doesn't care about the specific type of fruit, you can introduce an intermediate type between `Fruit` and these specific fruit types. For example `Apple` and `Banada` can inherit from a new type `SpecificFruit` which inherits `Fruit`. Then `Fruit` and `DriedFruit` can remain unchanged. Alternatively you can implement `fruit_type` for `DriedFruit` such that it returns a distinct enum value for that type, just like all the other fruit types do. – François Andrieux Nov 29 '17 at 20:53
  • @FrançoisAndrieux unfortunately it is exact same problem with DriedFruit as with Fruit - it sometimes cares about which fruit type it is and sometimes doesn't, just as Fruit. This solution would be perfect solution (and it is for the question as it is originally posted), but the fact that there is already something inheriting from Fruit complicates things. – Ilya Kobelevskiy Nov 29 '17 at 20:57
  • @IlyaKobelevskiy You should edit the question to express this, and provide a code snippet to illustrate it. – François Andrieux Nov 29 '17 at 21:01
  • 1
    @IlyaKobelevskiy: I'm no expect in virtual inheritance, but it would handle this situation. If `DriedFruit` virtually inherits from `Fruit`, and `DriedApple` virtually inherits from both `Fruit` and `DriedFruit`, then `DriedApple` overriding an abstract method of `Fruit` would apply to both `Fruit` and `DriedFruit`. [Live Demo](https://ideone.com/FFRzw2) – Remy Lebeau Nov 29 '17 at 21:06
0

Your question is quite abstract regarding the requirements.

Though your edited clarifications direct a way

The main use case is as follows - in my applications, there are certain methods that only expect Apple as an input, or only expect Orange as an input, and many methods that do not care which fruit it is.

I'm thinking of a completely different system based on interfaces and tag interfaces
(see the full Live Demo).

Define a common interface for all fruits first:

// A basic interface common for all fruits
struct IFruit {
  virtual ~IFruit() {}
  virtual std::string category() const = 0;
  virtual std::string common_name() const = 0;
  virtual std::string botanical_name() const = 0;
};

// An overload for the output operator is just nifty
std::ostream& operator<<(std::ostream& os, const IFruit& fruit) {
    os << "Category       : " << fruit.category() << std::endl;
    os << "Common Name    : " << fruit.common_name() << std::endl;
    os << "Botanical Name : " << fruit.botanical_name() << std::endl;
    return os;
}

Define tag interfaces to distinguish your particular types (Apples, Oranges):

// Tag interfaces to distinguish (not necessarily empty)
struct IApple : public IFruit {
  virtual ~IApple() {}
};

struct IOrange : public IFruit {
  virtual ~IOrange () {}
};

These should require to implement the IFruit interface implicitly.


Now you can provide an abstract base class, which implements the IFruit interface.
This base class is abstract in the sense the constructor function is hidden from the public scope, and needs to be called by an inheriting class constructor:

// Abstract base class implementation
template<class TagInterface>
class FruitBase : public TagInterface {
protected:
      std::string category_;
      std::string common_name_;
      std::string botanical_name_;

      FruitBase ( const std::string& category
                , const std::string& common_name
                , const std::string botanical_name) 
          : category_(category), common_name_(common_name)
          , botanical_name_(botanical_name) 
      {}

public:
      virtual ~FruitBase () {}
      virtual std::string category() const { return category_; }
      virtual std::string common_name() const { return common_name_; }
      virtual std::string botanical_name() const { return botanical_name_; }
};

Add additional tag interfaces as needed:

 struct IDriedApple : public IApple {
     virtual ~IDriedApple() {}
     virtual int rest_humidity() const = 0;
 };

Now you can create your concrete implementations with quite narrow class defintions:

// Concrete apples
struct Boskop : public FruitBase<IApple> {
public:
     Boskop() : FruitBase("Apples","Boskop","Malus domestica 'Belle de Boskoop'") {}
};

struct Braeburn : public FruitBase<IApple> {
public:
     Braeburn() : FruitBase("Apples","Braeburn","Malus domestica") {}
};

// Concrete oranges
struct Valencia : public FruitBase<IOrange> {
public:
     Valencia() : FruitBase("Oranges","Valencia","Citrus × sinensis") {}
};

struct Navel : public FruitBase<IOrange> {
public:
     Navel() : FruitBase("Oranges","Navel","Citrus × sinensis") {}
};

Here's what I assume is your function declarations specialized on taking Apples or Oranges only:

void aFunctionThatTakesOnlyApples(IApple& anApple) {
    std::cout << "This is an apple:" << std::endl;
    std::cout << anApple;
}

void aFunctionThatTakesOnlyOranges(IOrange& anOrange) {
    std::cout << "This is an orange:" << std::endl;
    std::cout << anOrange << std::endl;
}

This is a simple template function to query a known instance of IFruit for implementing a specific tag interface: template TagInterface* queryTagInterface(IFruit* fruit) { return dynamic_cast(fruit); }


And this is how you use all of that in action:

int main() {
    std::vector<std::unique_ptr<IFruit>> allFruits;
    allFruits.push_back(std::make_unique<Boskop>());
    allFruits.push_back(std::make_unique<Braeburn>());
    allFruits.push_back(std::make_unique<Valencia>()); 
    allFruits.push_back(std::make_unique<Navel>());
    for(auto& fruit : allFruits) {
        if(IApple* anApple = queryTagInterface<IApple>(fruit.get())) {
            aFunctionThatTakesOnlyApples(*anApple);
        }
        if(IOrange* anOrange = queryTagInterface<IOrange>(fruit.get())) {
            aFunctionThatTakesOnlyOranges(*anOrange);
        }
        std::cout << "-----------------------------------------------" << std::endl;
    }    
}

It feels unsafe/obscure to pass Fruit to method that only expects Apple, at the same time there are many methods that do not care which type it is so having 3 distinct types is not a good option either.

I should note that I also still don't understand what makes Apples and Oranges that different Fruits that they really deserve their own types. But well, that might be appropriate for less abstract metaphors of polymorphic class design and useful for concrete class hierarchy designs.

user0042
  • 7,917
  • 3
  • 24
  • 39