Ilya Kobelevskiy
Ilya Kobelevskiy

Reputation: 5345

Create distinct type for class with specific member variable value

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.

Upvotes: 4

Views: 601

Answers (3)

user0042
user0042

Reputation: 8018

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.

Upvotes: 0

Killzone Kid
Killzone Kid

Reputation: 6240

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

Upvotes: 2

Jodocus
Jodocus

Reputation: 7581

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.

Upvotes: 3

Related Questions