Full disclaimer first: I have not compiled this example code, nor the real code (well, at least fully deployed). I am still wrapping my head around the problem. With that in mind, say we have this class structure:
A super base class that we will use to store instances with this base in the same container and a few "Facet" classes that we will use with multiple inheritance to encapsulate common behaviour.
class Facet_A;
class Facet_B;
class Facet_C;
struct Facet_converter
{
Facet_A * facet_a;
Facet_B * facet_b;
Facet_C * facet_c;
};
class Super_base
{
public:
virtual ~Super_base() {}
virtual Facet_converter convert()=0;
virtual const Facet_converter convert()const=0; //Notice this const...
};
class Facet_A
{
private:
int value_a;
public:
virtual ~Facet_A() {}
Facet_A():value_a(0) {}
void set_value_a(int v) {value_a=v;}
int get_value_a() const {return value_a;}
};
class Facet_B
{
private:
float value_b;
public:
Facet_B():value_b(0) {}
virtual ~Facet_B() {}
void set_value_b(float v) {value_b=v;}
float get_value_b() const {return value_b;}
};
class Facet_C
{
private:
char value_c;
public:
Facet_C():value_c('a') {}
virtual ~Facet_C() {}
void set_value_c(char v) {value_c=v;}
char get_value_c() const {return value_c;}
};
All classes that derive from these will always:
- Use Super_base as a public base class, so we can store them in a vector of these.
- Implement the convert methods that will return a Facet_converter object with pointers (shared, unique, raw, whatever) of the derived class casted as a particular facet (null, if not applicable).
- Use Facet_A, Facet_B or Facet_C as a base class depending on what do they try to implement.
The client code would do something like...
std::vector<Super_base *> v;
//Fill super base with the good stuff.
//Let's use everything that has an integer!.
for(auto sb : v)
{
Facet_converter fc=sb->convert();
if(fc.facet_a)
{
//Do something with this integer like... std::cout<<fc.facet_a->get_value_a()<<std::endl;
}
}
//Let's use everything that has a float.
for(auto sb : v)
{
Facet_converter fc=sb->convert();
if(fc.facet_b)
{
//Do something with this float...
}
}
//Let's use everything that has a char.
for(auto sb : v)
{
Facet_converter fc=sb->convert();
if(fc.facet_c)
{
//You get the drift...
}
}
Horrible design apart (I've come to this point sick of visitors everywhere) this particular example is pretty much barebones, but you get what I am trying to do: casting down the hierarchy without using dynamic_cast and "enforcing" the compiler help (it would yell at me if I tried an assignment to a non-base class in the "convert" method).
So, a of fully implemented class...
class Derived_numeric: //This one has a float and and int
public Super_base,
public Facet_A,
public Facet_B
{
///Blah blah blah blah
virtual Facet_converter convert()
{
Facet_converter result;
result.facet_a=this;
result.facet_b=this;
result.facet_c=nullptr; //Assume no constructor for the struct that initializes the method, not really the case.
return result;
}
virtual const Facet_converter convert()const
{
const Facet_converter result;
result.facet_a=this; //Booom!!!. Error, const Derived_numeric can't be caster to Facet_A because... it's const.
result.facet_b=this;
result.facet_c=nullptr;
return result;
}
}
And there's the problem, right in the const convert method. There's a const and a non const method because the client code may work with const and non const objects but there's no way the compiler is gonna let me assign a "const this" without const casting it first.
Considering that I've come with two solutions:
- const_casting the this pointer in the const method.
- Creating two Facet_converter objects: Facet_converter and Facet_converter_const. They're exactly the same but one has const pointers and the other has regular pointers. Let the client code use the one they need.
Both of them suffer from horrible code repetition, since the code is almost the same and only a few details change.
I've toyed with the idea of implementing only the const one, const_casting the "this" pointer and basically lying about what the method promises. Want true constness?, add the const modifier to the result of convert() and be done with it... Seems easier, but too sneaky.
My question is, can I implement this idea without basically copying and pasting the code and being sneaky?. Remember that I need both const and non const (the derived object may change its state by using the facets, or it may not).
Now, please consider that I am not looking for "Your approach is wrong" or "I don't know why you would want to do that". This is the current situation I want to deal with and learn about. I already know I can use double dispatching or I can bastardize the whole base class to contain every other possibility... I am just looking for alternatives to it.