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.