0
votes

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.

1
you can always add const to facet_converter struct...tp1
Is that the part where it reads "want true constness" in the question?. I am considering that already but there's always the "sneaky" part...The Marlboro Man
"Horrible design apart..." - and there is the problem. Back to the drawing board for you sonny. This class is so complicated that your consumers will never be willing to use it and you will never be able to maintain it.Richard Hodges
Thanks Richard but, as said, this is a problem I want to solve and "your approach is wrong" does not apply by definition - think of it as an exercise of sorts. Don't worry about my consumers, as there are none (this is never going to end up in a codebase meant to be used). In the last paragraph there are two other approaches I used before but this is the one I'd like to try now, convoluted as it seems. Again, think of it as an exercise (the git branch is called "experiment") if you will. Still, naming any other approaches is welcome, as I am bound to try them eventually. Thanks.The Marlboro Man
@RichardHodges, I created the following chat room chat.stackoverflow.com/rooms/85492/… in case you want to come in and discuss alternative approaches. Your profile mentions 30+ years of experience in game making, perhaps you can provide me with hints there. Thanks.The Marlboro Man

1 Answers

1
votes

You could make a const Facet_converter member of Super_base and then set it via a constructor.

class Super_base
{
protected:
    const Facet_converter implementations;

public:
    Super_base( const Facet_converter& implementations )
        : implementations( implementations ) {};
    virtual ~Super_base() {};
    const Facet_converter& convert() const { return implementations; }
};

When you implement the derived class, do:

Derived_numeric::Derived_numeric( ) : Super_base( Facet_converter( this, this, NULL ) )

You also need to add a constructor for the struct so that call is possible:

struct Facet_converter
{
    Facet_converter( Facet_A* const& a, Facet_B* const& b, Facet_C* const& c )
    {
        facet_a = a;
        facet_b = b;
        facet_c = c;
    }

    Facet_A * facet_a;
    Facet_B * facet_b;
    Facet_C * facet_c;
};

I haven't tested this using actual pointers and subclasses, so it might need some tweaks.