Let's say I have this class
struct IDebug {
virtual void print(std::ostream&) const = 0;
};
and the prints should follow some format defined for each class implementing IDebug.
the format is static in the sense that for a certain implementation will always have the same format and that I want the format to be used regardless of whether I have any instance of the class (for example at program initialization)
so I added a:
static std::string format();
to each of the implementing class. ugly (because it's not forced by the interface) but I could live with that
now I wanted to add some validation in the interface level that the actual print() is following the format this class defines. in order to do that I had to make a modification:
struct IDebug {
void print(std::ostream& o) const { // no longer virtual
auto format = format_impl();
// preprocess using format
print_impl(o);
// postprocess using format
}
protected:
virtual void print_impl(std::ostream& o) const = 0;
virtual std::string format_impl() const = 0;
};
and each of the implementing classes now have the exact same line of code:
std::string format_impl() const override { return format(); }
struct MooDebug : public IDebug {
// rest of the code
static std::string format() { return "whatever"; } // this was already here. sadly the interface couldn't force it
std::string format_impl() const override { return format(); } // all implementing classes are repeating this exact line
};
I'm looking for advice how to avoid it or make it better.
You could make use of some CRTP like class to create a base class that does what you want:
struct MooDebug : DebugHelper<MooDebug>
{
static std::string format() { return "whatever"; }
};
template<typename T>
struct DebugHelper : IDebug
{
std::string format_impl() const override { return T::format(); }
};
Note that in CRTP you actually cast your this pointer to T& and call a method that way.
Alternative in C++17 (haven't tested this), you can pass values to a template. As I never did this with strings, it might not work.
template<auto T>
struct DebugHelper : IDebug
{
static std::string format() { return T; }
std::string format_impl() const override { return T; }
};
struct MooDebug : DebugHelper< "whatever">
{};
based on #JVApen answer I ended up doing this:
template <class T>
struct IDebug {
void print(std::ostream& o) const { // no longer virtual
auto myformat = T::format();
// preprocess using format
print_impl(o);
// postprocess using format
}
protected:
virtual void print_impl(std::ostream& o) const = 0;
};
now we also force (up to not using the CRTP correctly) the static implementation of format() which is a nice added bonus
Related
I have an ISerializable interface with pure virtual read and write functions. I also have a List<T> class. So, my question is: can I inherit my List<T> from ISerializable only if T is also inherit from ISerializable?
usage example:
#include <array>
#include <iostream>
class ISerializable
{
public:
virtual void read(std::istream& stream) = 0;
virtual void write(std::ostream& stream) = 0;
};
class A : public ISerializable {
public:
void read(std::istream& stream) override {}
void write(std::ostream& stream) override {}
};
class B {};
template<typename T>
class List : public ISerializable
{
public:
void read(std::istream& stream) override
{
for (int i = 0; i < items.size(); i++)
{
items[i].read(stream);
}
}
void write(std::ostream& stream) override
{
for (int i = 0; i < items.size(); i++)
{
items[i].write(stream);
}
}
private:
std::array<T, 10> items;
};
int main()
{
List<A> list_a;
//List<B> list_b; <- error here
}
You can indeed set up something like that up in C++20.
template<typename T>
class List : public ListCommon<T> {
};
template<std::derived_from<ISerializable> S>
class List<S> : public ListCommon<S>, public ISerializable {
// Implement the serialization
};
The specialization for a serialziable type is only viable with the added constraint of the type being derived from ISerializable (checked with the standard std::derived_from concept).
ListCommon is where I would place the common implementation details to avoid repeating them upon specialization.
Ok, this solution fits if you already have a lot of classes that you don't really want to refactor and/or duplicate. The disadvantage here is that IDE will not be able to assist you sometimes. However, since it implies one of main features of concepts, it is still legal. Just be careful!
Here we have an interface that explicitly requires some functions to be implemented and a concept that also requires same functions to be implemented. Prefer interface over the concept! If you inherit interface, IDE will assist you in function overloading. Concept is needed if you want to use interface as a parameter type later (see utility class example). This is an example of duck typing.
class ISerializable
{
public:
virtual void write_to_stream(std::ostream& stream) const = 0;
virtual void read_from_stream(std::istream& stream) = 0;
};
template<typename T>
concept ImplicitlySerializable = requires(const T a1, T a2, std::ostream os, std::istream is)
{
a1.write_to_stream(os);
a2.read_from_stream(is);
};
template<typename T>
concept Serializable = std::derived_from<T, ISerializable> || ImplicitlySerializable<T>;
Some utility functions to handle any object that match concept
class StreamUtils
{
public:
template<Serializable T>
static void write(std::ostream& stream, const T& value)
{
value.write_to_stream(stream);
}
template<Serializable T>
static void read(std::istream& stream, T& to)
{
to.read_from_stream(stream);
}
};
Usage example:
// Interface usage
class Animal : public ISerializable
{
void write_to_stream(std::ostream& stream) override
{
// serialization code here
}
void read_from_stream(std::istream& stream) override
{
// deserialization code here
}
}
// Concept usage
template<typename T>
class List
{
void write_to_stream(std::ostream& stream) const requires Serializable<T>
{
// serialization code here
}
void read_from_stream(std::istream& stream) requires Serializable<T>
{
// deserialization code here
}
{
int main()
{
std::ofstream file("my_file.bin", std::ios::out | std::ios:binary);
StreamUtils::write(file, Animal());
StreamUtils::write(file, List<Animal>());
}
class A
{
friend void foo();
virtual void print_Var() const{};
};// does not contain variable Var;
template<class T>
class B : public A
{
T Var;
public:
B(T x):Var(x){}
void print_Var() const override
{
std::cout<<Var<<std::endl;
}
};
void foo()
{
std::array<std::unique_ptr<A>, 3> Arr = {
std::make_unique<B<int>>(100),
std::make_unique<B<int>>(20),
std::make_unique<B<std::string>>("Hello Stackoverflow")
};
std::shuffle(Arr.begin(), Arr.end(), std::mt19937(std::random_device()())); // 3rd parameter generated by Clang-Tidy
for (auto &i: Arr)
{
i->print_Var(); // OK
// auto z = i->Var // no member named Var in A
// obviously base class does not contain such variable
// if (i->Var==20) {/* do something*/}
// if (i->Var=="Hello Stackoverflow") {/* do something*/}
}
}
Explanation:
I want to iterate over array of pointers to A, which is filled with pointers to classes derived from A, and depending on what type is variable Var, do some if( ) statement.
Problem is that i cannot access Var, cause its not member of base class. However, it's possible to cout those values by, for example, overloaded function returning void. Could i write function in A class that returns templated type? like:
class A
{
<class T> GetVar()
}
Besides, I feel like I'm dealing with this problem in totally improper way. Can i mix templates and inheritance like that? If not, how should it be designed?
You have a few choices. I'll explain my preferred solution first.
1. Use dynamic dispatch
If you have an array of a base class type, why do you even want to do stuff with Var? That variable is specific to the child class. If you have a A somewhere, you shouldn't even care what B has or hasn't at that place.
Operations on the typed variable should be encapsulated in virtual function in the base class. If you want to do condition and stuff, maybe you could encapsulate that condition into a virtual function that returns a boolean.
2a. Drop the base class and use variant
Sometimes, you know in advance the amount of types that will go into that list. Using a variant and drop the base class is a good solution that may apply to your case.
Let's say you only have int, double and std::string:
using poly = std::variant<B<int>, B<double>, B<std::string>>;
std::array<poly, 3> arr;
arr[0] = B<int>{};
arr[1] = B<double>{};
arr[2] = B<std::string>{};
// arr[2] = B<widget>{}; // error, not in the variant type
std::visit(
[](auto& b) {
using T = std::decay_t<decltype(b)>;
if constexpr (std::is_same_v<B<int>, T>) {
b.Var = 2; // yay!
}
},
arr[0]
);
2b. Drop the base class and use generic functions
Drop the base class entirely, and template your functions that do operation on them. You can move all your function into an interface or many std::function. Operate on that instead of the function directly.
Here's an example of what I meant:
template<typename T>
void useA(T const& a) {
a.Var = 34; // Yay, direct access!
}
struct B {
std::function<void()> useA;
};
void createBWithInt() {
A<int> a;
B b;
b.useA = [a]{
useA(a);
};
};
This is fine for cases where you only have few operations. But it can quickly lead to code bloat if you have a lot of operations or if you have many types of std::function.
3. Use a visitor
You could create a visitor that dispatch to the right type.
This solution would be much close to what you except, but is quite combersome and can break easily when adding cases.
Something like this:
struct B_Details {
protected:
struct Visitor {
virtual accept(int) = 0;
virtual void accept(double) = 0;
virtual void accept(std::string) = 0;
virtual void accept(some_type) = 0;
};
template<typename T>
struct VisitorImpl : T, Visitor {
void accept(int value) override {
T::operator()(value);
}
void accept(double) override {
T::operator()(value);
}
void accept(std::string) override {
T::operator()(value);
}
void accept(some_type) override {
T::operator()(value);
}
};
};
template<typename T>
struct B : private B_Details {
template<typename F>
void visit(F f) {
dispatch_visitor(VisitorImpl<F>{f});
}
private:
virtual void dispatch_visitor(Visitor const&) = 0;
};
// later
B* b = ...;
b->visit([](auto const& Var) {
// Var is the right type here
});
Then of course, you have to implement the dispatch_visitor for each child class.
4. Use std::any
This is litteraly returning the variable with type erasure. You cannot do any operation on it without casting it back:
class A {
std::any GetVar()
};
I personnaly don't like this solution because it can break easily and is not generic at all. I would not even use polymorphism in that case.
I think it will be the easiest way. Just move the comparison method to the interface and override it in derived classes. Add the following lines to yor example:
class A
{
/*..................................................*/
virtual bool comp(const int) const { return false; }
virtual bool comp(const std::string) const { return false; }
virtual bool comp(const double) const { return false; }
};
template<class T>
class B : public A
{
/*..................................................*/
virtual bool comp(const T othr) const override { return othr == Var; }
};
void foo()
{
/*..................................................*/
if (i->comp(20))
{
/* do something*/
}
if (i->comp("Hello Stackoverflow"))
{
/* do something*/
}
/*..................................................*/
}
class A
{
friend void foo();
virtual void print_Var() const{};
};// does not contain variable Var;
template<class T>
class B : public A
{
T Var;
public:
B(T x):Var(x){}
void print_Var() const override
{
std::cout<<Var<<std::endl;
}
};
void foo()
{
std::array<std::unique_ptr<A>, 3> Arr = {
std::make_unique<B<int>>(100),
std::make_unique<B<int>>(20),
std::make_unique<B<std::string>>("Hello Stackoverflow")
};
std::shuffle(Arr.begin(), Arr.end(), std::mt19937(std::random_device()())); // 3rd parameter generated by Clang-Tidy
for (auto &i: Arr)
{
i->print_Var(); // OK
// auto z = i->Var // no member named Var in A
// obviously base class does not contain such variable
// if (i->Var==20) {/* do something*/}
// if (i->Var=="Hello Stackoverflow") {/* do something*/}
}
}
Explanation:
I want to iterate over array of pointers to A, which is filled with pointers to classes derived from A, and depending on what type is variable Var, do some if( ) statement.
Problem is that i cannot access Var, cause its not member of base class. However, it's possible to cout those values by, for example, overloaded function returning void. Could i write function in A class that returns templated type? like:
class A
{
<class T> GetVar()
}
Besides, I feel like I'm dealing with this problem in totally improper way. Can i mix templates and inheritance like that? If not, how should it be designed?
You have a few choices. I'll explain my preferred solution first.
1. Use dynamic dispatch
If you have an array of a base class type, why do you even want to do stuff with Var? That variable is specific to the child class. If you have a A somewhere, you shouldn't even care what B has or hasn't at that place.
Operations on the typed variable should be encapsulated in virtual function in the base class. If you want to do condition and stuff, maybe you could encapsulate that condition into a virtual function that returns a boolean.
2a. Drop the base class and use variant
Sometimes, you know in advance the amount of types that will go into that list. Using a variant and drop the base class is a good solution that may apply to your case.
Let's say you only have int, double and std::string:
using poly = std::variant<B<int>, B<double>, B<std::string>>;
std::array<poly, 3> arr;
arr[0] = B<int>{};
arr[1] = B<double>{};
arr[2] = B<std::string>{};
// arr[2] = B<widget>{}; // error, not in the variant type
std::visit(
[](auto& b) {
using T = std::decay_t<decltype(b)>;
if constexpr (std::is_same_v<B<int>, T>) {
b.Var = 2; // yay!
}
},
arr[0]
);
2b. Drop the base class and use generic functions
Drop the base class entirely, and template your functions that do operation on them. You can move all your function into an interface or many std::function. Operate on that instead of the function directly.
Here's an example of what I meant:
template<typename T>
void useA(T const& a) {
a.Var = 34; // Yay, direct access!
}
struct B {
std::function<void()> useA;
};
void createBWithInt() {
A<int> a;
B b;
b.useA = [a]{
useA(a);
};
};
This is fine for cases where you only have few operations. But it can quickly lead to code bloat if you have a lot of operations or if you have many types of std::function.
3. Use a visitor
You could create a visitor that dispatch to the right type.
This solution would be much close to what you except, but is quite combersome and can break easily when adding cases.
Something like this:
struct B_Details {
protected:
struct Visitor {
virtual accept(int) = 0;
virtual void accept(double) = 0;
virtual void accept(std::string) = 0;
virtual void accept(some_type) = 0;
};
template<typename T>
struct VisitorImpl : T, Visitor {
void accept(int value) override {
T::operator()(value);
}
void accept(double) override {
T::operator()(value);
}
void accept(std::string) override {
T::operator()(value);
}
void accept(some_type) override {
T::operator()(value);
}
};
};
template<typename T>
struct B : private B_Details {
template<typename F>
void visit(F f) {
dispatch_visitor(VisitorImpl<F>{f});
}
private:
virtual void dispatch_visitor(Visitor const&) = 0;
};
// later
B* b = ...;
b->visit([](auto const& Var) {
// Var is the right type here
});
Then of course, you have to implement the dispatch_visitor for each child class.
4. Use std::any
This is litteraly returning the variable with type erasure. You cannot do any operation on it without casting it back:
class A {
std::any GetVar()
};
I personnaly don't like this solution because it can break easily and is not generic at all. I would not even use polymorphism in that case.
I think it will be the easiest way. Just move the comparison method to the interface and override it in derived classes. Add the following lines to yor example:
class A
{
/*..................................................*/
virtual bool comp(const int) const { return false; }
virtual bool comp(const std::string) const { return false; }
virtual bool comp(const double) const { return false; }
};
template<class T>
class B : public A
{
/*..................................................*/
virtual bool comp(const T othr) const override { return othr == Var; }
};
void foo()
{
/*..................................................*/
if (i->comp(20))
{
/* do something*/
}
if (i->comp("Hello Stackoverflow"))
{
/* do something*/
}
/*..................................................*/
}
class A
{
friend void foo();
virtual void print_Var() const{};
};// does not contain variable Var;
template<class T>
class B : public A
{
T Var;
public:
B(T x):Var(x){}
void print_Var() const override
{
std::cout<<Var<<std::endl;
}
};
void foo()
{
std::array<std::unique_ptr<A>, 3> Arr = {
std::make_unique<B<int>>(100),
std::make_unique<B<int>>(20),
std::make_unique<B<std::string>>("Hello Stackoverflow")
};
std::shuffle(Arr.begin(), Arr.end(), std::mt19937(std::random_device()())); // 3rd parameter generated by Clang-Tidy
for (auto &i: Arr)
{
i->print_Var(); // OK
// auto z = i->Var // no member named Var in A
// obviously base class does not contain such variable
// if (i->Var==20) {/* do something*/}
// if (i->Var=="Hello Stackoverflow") {/* do something*/}
}
}
Explanation:
I want to iterate over array of pointers to A, which is filled with pointers to classes derived from A, and depending on what type is variable Var, do some if( ) statement.
Problem is that i cannot access Var, cause its not member of base class. However, it's possible to cout those values by, for example, overloaded function returning void. Could i write function in A class that returns templated type? like:
class A
{
<class T> GetVar()
}
Besides, I feel like I'm dealing with this problem in totally improper way. Can i mix templates and inheritance like that? If not, how should it be designed?
You have a few choices. I'll explain my preferred solution first.
1. Use dynamic dispatch
If you have an array of a base class type, why do you even want to do stuff with Var? That variable is specific to the child class. If you have a A somewhere, you shouldn't even care what B has or hasn't at that place.
Operations on the typed variable should be encapsulated in virtual function in the base class. If you want to do condition and stuff, maybe you could encapsulate that condition into a virtual function that returns a boolean.
2a. Drop the base class and use variant
Sometimes, you know in advance the amount of types that will go into that list. Using a variant and drop the base class is a good solution that may apply to your case.
Let's say you only have int, double and std::string:
using poly = std::variant<B<int>, B<double>, B<std::string>>;
std::array<poly, 3> arr;
arr[0] = B<int>{};
arr[1] = B<double>{};
arr[2] = B<std::string>{};
// arr[2] = B<widget>{}; // error, not in the variant type
std::visit(
[](auto& b) {
using T = std::decay_t<decltype(b)>;
if constexpr (std::is_same_v<B<int>, T>) {
b.Var = 2; // yay!
}
},
arr[0]
);
2b. Drop the base class and use generic functions
Drop the base class entirely, and template your functions that do operation on them. You can move all your function into an interface or many std::function. Operate on that instead of the function directly.
Here's an example of what I meant:
template<typename T>
void useA(T const& a) {
a.Var = 34; // Yay, direct access!
}
struct B {
std::function<void()> useA;
};
void createBWithInt() {
A<int> a;
B b;
b.useA = [a]{
useA(a);
};
};
This is fine for cases where you only have few operations. But it can quickly lead to code bloat if you have a lot of operations or if you have many types of std::function.
3. Use a visitor
You could create a visitor that dispatch to the right type.
This solution would be much close to what you except, but is quite combersome and can break easily when adding cases.
Something like this:
struct B_Details {
protected:
struct Visitor {
virtual accept(int) = 0;
virtual void accept(double) = 0;
virtual void accept(std::string) = 0;
virtual void accept(some_type) = 0;
};
template<typename T>
struct VisitorImpl : T, Visitor {
void accept(int value) override {
T::operator()(value);
}
void accept(double) override {
T::operator()(value);
}
void accept(std::string) override {
T::operator()(value);
}
void accept(some_type) override {
T::operator()(value);
}
};
};
template<typename T>
struct B : private B_Details {
template<typename F>
void visit(F f) {
dispatch_visitor(VisitorImpl<F>{f});
}
private:
virtual void dispatch_visitor(Visitor const&) = 0;
};
// later
B* b = ...;
b->visit([](auto const& Var) {
// Var is the right type here
});
Then of course, you have to implement the dispatch_visitor for each child class.
4. Use std::any
This is litteraly returning the variable with type erasure. You cannot do any operation on it without casting it back:
class A {
std::any GetVar()
};
I personnaly don't like this solution because it can break easily and is not generic at all. I would not even use polymorphism in that case.
I think it will be the easiest way. Just move the comparison method to the interface and override it in derived classes. Add the following lines to yor example:
class A
{
/*..................................................*/
virtual bool comp(const int) const { return false; }
virtual bool comp(const std::string) const { return false; }
virtual bool comp(const double) const { return false; }
};
template<class T>
class B : public A
{
/*..................................................*/
virtual bool comp(const T othr) const override { return othr == Var; }
};
void foo()
{
/*..................................................*/
if (i->comp(20))
{
/* do something*/
}
if (i->comp("Hello Stackoverflow"))
{
/* do something*/
}
/*..................................................*/
}
Given the following type hierarchy
class Base { public: virtual ~Base(); }
class OurDervied : public Base {}
class TheirDerived : public Base {}
class General { public: virtual ~General(); }
class MySpecial : public General {};
class YourSpecial : public General {};
I have a function f(Base *bp).
In f, I want to create an object with type that depends on the type passed in. For example, f creates a MySpecial when receiving an instance of OurDerived, and creates a YourSpecial when receiving an instance of TheirDerived.
I think I can do this with dynamic_cast. It probably requires trying to cast the received object repeatedly until a match is found (non-nullptr returned).
Another option is giving OurDerived, TheirDerived, etc a unique tag and then use a switch case construct to create MySpecial, YourSpecial, etc.
Are there any other options for mapping class types in C++?
Manual Type Switching
If the types you want to create have no common ancestor, you have no other option but use a
if (dynamic_cast<const DerivedA *>(&base))
{
// Create an object of some type.
}
else if (dynamic_cast<const DerivedB *>(&base))
{
// Create an object of some other type.
}
else if (dynamic_cast<const DerivedC *>(&base))
{
// Create an object of yet aother type.
}
else
{
// Handle the case that no type was matched. Maybe use a default or
// issue an error.
}
cascade and there is no direct way you can return the created object because a function cannot decide at run-time what return type it wants to have. The only way out would be to use type erasure or ugly unions.
Lookup Table with Factory Functions
Fortunately, this is not what you have to do if all the types you want to create are derived from a common base class, as you have indicated in the comments. In this case, you can map the typeid of an object to a factory function that creates the appropriate object. As usual with run-time polymorphism, this requires a heap allocation.
void
take_action(const Base& base)
{
using FactoryT = std::function<std::unique_ptr<General>()>;
static const std::map<std::type_index, FactoryT> factories {
{typeid(DerivedA), [](){ return std::make_unique<Special1>(); }},
{typeid(DerivedB), [](){ return std::make_unique<Special2>(); }},
{typeid(DerivedC), [](){ return std::make_unique<Special3>(); }},
};
const auto o_uptr = factories.at(typeid(base))();
// Use the object. It can also be returned.
}
I have made the std::map<std::type_index, std::function<FactoryT()>> static so it is created only once for the entire run-time of the program. It is not clear whether or not this is beneficial in your particular situation. Maybe benchmark it.
Here is a complete working example.
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <typeindex>
#include <typeinfo>
struct Base
{
virtual ~Base() = default;
virtual std::string
name() const
{
return "Base";
}
};
std::ostream&
operator<<(std::ostream& os, const Base& base)
{
return os << base.name();
}
template<char Token>
struct Derived : Base
{
virtual std::string
name() const override
{
std::string name {"Derived"};
name += Token;
return name;
}
};
using DerivedA = Derived<'A'>;
using DerivedB = Derived<'B'>;
using DerivedC = Derived<'C'>;
struct General
{
virtual ~General() = default;
virtual std::string
name() const
{
return "General";
}
};
template<char Token>
struct Special : General
{
virtual std::string
name() const override
{
std::string name {"Special"};
name += Token;
return name;
}
};
std::ostream&
operator<<(std::ostream& os, const General& general)
{
return os << general.name();
}
using Special1 = Special<'1'>;
using Special2 = Special<'2'>;
using Special3 = Special<'3'>;
void
take_action(const Base& base)
{
using FactoryT = std::function<std::unique_ptr<General>()>;
static const std::map<std::type_index, FactoryT> factories {
{typeid(DerivedA), [](){ return std::make_unique<Special1>(); }},
{typeid(DerivedB), [](){ return std::make_unique<Special2>(); }},
{typeid(DerivedC), [](){ return std::make_unique<Special3>(); }},
};
const auto o_uptr = factories.at(typeid(base))();
std::cout << base << " was mapped to " << *o_uptr << std::endl;
}
int
main()
{
take_action(DerivedA {});
take_action(DerivedB {});
take_action(DerivedC {});
return 0;
}
Output:
DerivedA was mapped to Special1
DerivedB was mapped to Special2
DerivedC was mapped to Special3
Visitor Pattern
Of course, you should ask yourself the question why you actually want to do this. There are for sure legitimate applications of this technique but taking an abstract type and then taking action based on its dynamic type is usually a sign of over-abstraction and makes for poorly maintainable code. Did you consider adding the factory directly to Base?
struct Base
{
virtual ~Base() = default;
virtual std::unique_ptr<General>
getDealer() = 0;
// ...
};
The Derived classes can then override getDealer to do what the factories lambdas did in the above example.
If this seems to intrusive (maybe the Base class shouldn't know anything about the General class at all), you could consider using the visitor pattern. It is a bit more work but allows for better decoupling. There is plenty of information available on this pattern so I'll only show its application to your specific problem and refer you to your favorite search engine if you need more explanation.
#include <iostream>
#include <memory>
#include <string>
struct BaseVisitor;
struct Base
{
virtual ~Base() = default;
virtual void
accept(BaseVisitor&) const = 0;
virtual std::string
name() const
{
return "Base";
}
};
std::ostream&
operator<<(std::ostream& os, const Base& base)
{
return os << base.name();
}
template<char Token>
struct Derived : Base
{
virtual void
accept(BaseVisitor& vtor) const override;
virtual std::string
name() const override
{
std::string name {"Derived"};
name += Token;
return name;
}
};
using DerivedA = Derived<'A'>;
using DerivedB = Derived<'B'>;
using DerivedC = Derived<'C'>;
struct BaseVisitor
{
virtual ~BaseVisitor() = default;
virtual void
visit(const DerivedA&) = 0;
virtual void
visit(const DerivedB&) = 0;
virtual void
visit(const DerivedC&) = 0;
};
// Cannot be defined earlier because we need the complete type of BaseVisitor.
template<char Token>
void
Derived<Token>::accept(BaseVisitor& vtor) const
{
vtor.visit(*this);
}
struct General
{
virtual ~General() = default;
virtual std::string
name() const
{
return "General";
}
};
template<char Token>
struct Special : General
{
virtual std::string
name() const override
{
std::string name {"Special"};
name += Token;
return name;
}
};
std::ostream&
operator<<(std::ostream& os, const General& general)
{
return os << general.name();
}
using Special1 = Special<'1'>;
using Special2 = Special<'2'>;
using Special3 = Special<'3'>;
void
take_action(const Base& base)
{
struct Mapper : BaseVisitor
{
std::unique_ptr<General> uptr {};
virtual void
visit(const DerivedA&) override
{
this->uptr.reset(new Special1 {});
}
virtual void
visit(const DerivedB&) override
{
this->uptr.reset(new Special2 {});
}
virtual void
visit(const DerivedC&) override
{
this->uptr.reset(new Special3 {});
}
};
Mapper visitor {};
base.accept(visitor);
std::cout << base << " was mapped to " << *visitor.uptr << std::endl;
}
int
main()
{
take_action(DerivedA {});
take_action(DerivedB {});
take_action(DerivedC {});
return 0;
}
Note how we have nicely broken the coupling between Base and General. On the down side, we had to introduce some kind of parent-to-child dependency via the BaseVisitor class.
This solution also gets completely rid of any explicit run-time type inference and elegantly lets the dynamic dispatch machinery do all the magic behind the scenes.
Yes, you can delegate type mapping to derived classes:
class Base
{
public:
virtual General* map() = 0;
};
class OurDerived: public Base
{
protected:
General* map()
{
// compute Type* for OurDerved
}
};
class TheirDerived: public Base
{
protected:
General* map()
{
// compute Type* for TheirDerived
}
};
It's hard to say without knowing what responsibilities your function has, or how you feel about coupling {My|Your}Special to {Our|Their}Derived.
Is Base constructible? Is Base or its derived classes allowed to have virtual methods? If you already incurred the cost of a vtable, I would delegate the responsibility to the derived types themselves, and explicitly make the method abstract on Base to force each derivation to explain itself in this regard.
Are MySpcial / YourSpecial related in the type hierarchy? Otherwise you are better off experimenting with explicit template instantiations of a helper function.