tags:

views:

341

answers:

10
+2  Q: 

Automatic casts

I am currently suffering a brain fart. I've done this before but I can't remember the exact syntax and I can't look at the code I wrote because I was working at another company at the time. I have this arrangement:

class P
{
// stuff
};

class PW : public P
{
// more stuff
};

class PR : public P
{
// more stuff
};

class C
{
public:
    P GetP() const { return p; } 
private:
    P p;
};

// ...
    P p = c.GetP( ); // valid
    PW p = c.GetP( ); // invalid
    PR p = c.GetP( ); // invalid
// ...

Now I would like to make P interchangeable with PW and PR (and thus PW and PR can be interchanged). I could probably get away with casts but this code change has occurred quite a few times in this module alone. I am pretty sure it is a operator but for the life of me I can't remember what.

How do I make P interchangeable with PW and PR with minimal amount of code?

Update: To give a bit more clarification. P stands for Project and the R and W stands for Reader and Writer respectively. All the Reader has is the code for loading - no variables, and the writer has code for simply Writing. It needs to be separate because the Reading and Writing sections has various manager classes and dialogs which is none of Projects real concern which is the manipulation of project files.

Update: I also need to be able to call the methods of P and PW. So if P has a method a() and PW as a method call b() then I could :

PW p = c.GetP();
p.a();
p.b();

It's basically to make the conversion transparent.

+2  A: 

In the code above, you have the opposite of the slicing problem.

What you're trying to do is assign from a P to a PW or PR that contain more information than the source object. How do you do this? Say P only has 3 member variables, but PW has 12 additional members - where do the values for these come when you write PW p = c.GetP()?

If this assignment actually is valid, which should really indicate some kind of design weirdness, then I would implement PW::operator=(const P&) and PR::operator=(const P&), PW::PW(const P&) and PR::PR(const P&). But I wouldn't sleep too well that night.

Carl Seleborg
Technically it is the opposite of the slicing problem. Slicing is when you loose information copy assigning to a base class.
Martin York
Ah, you're right!
Carl Seleborg
Richard Corden
That's the point: if it is actually semantically justified, the assignment would have to be explicitly implemented with operator=(), which would do the right thing to avoid slicing and set all members correctly.
Carl Seleborg
PW and PR do not have any member variables, just member functions.
graham.reeds
A: 

Use a reference or pointer to P rather than an object:

class C
{
 public:
  P* GetP() const { return p; }
 private:
  P* p;
};

This will allow a PW* or a PR* to be bound to C.p. However, if you need to go from a P to a PW or PR, you need to use dynamic_cast<PW*>(p), which will return the PW* version of p, or NULL of p is not a PW* (for instance, because it's a PR*). Dynamic_cast has some overhead, though, and it's best avoided if possible (use virtuals).

You can also use the typeid() operator to determine the run-time type of an object, but it has several issues, including that you must include and that it can't detect extra derivation.

coppro
In the given example, dynamic_cast wouldn't work since the returned object actually is a P, not a PW or a PR (look at the type of C::p).
Carl Seleborg
I need to go from P to PW or PR. I think I might even need to go from PW to PR.
graham.reeds
A: 

Perhaps you mean the dynamic_cast operator?

Jon Grant
A: 

They are not fully interchangeable. PW is a P. PR is a P. But P is not necessarily a PW, and it is not necessarily a PR. You can use static_cast to cast pointers from PW * to P *, or from PR * to P *. You should not use static_cast to cast actual objects to their super-class because of "slicing". E. g. if you cast an object of PW to P the extra stuff in PW will be "sliced" off. You also cannot use static_cast to cast from P * to PW *. If you really have to do it, use dynamic_cast, which will check at run-time whether the object is actually of the right sub-class, and give you a run-time error if it is not.

Dima
A: 

I'm not sure what you mean exactly, but bear with me.

They kind of already are. Just call everything P and you can pretend PR and PW are P's. PR and PW are still different though.

Making all three equivalent would result in trouble with the Liskov principle. But then why would you give them different names if they are truly equivalent?

Rik
+3  A: 

You're trying to coerce actual variables, rather than pointers. To do that would require a cast. However, if your class definition looked like this:

class C

    {
        public: 
            P* GetP() const { return p; }
        private:
            P* p;
    }

Then, whether p was a pointer to a P, a PW, or a PR, your function wouldn't change, and any (virtual) functions called on the P* returned by the function would use the implementation from P, PW or PR depending on what the member p was..

I guess the key thing to remember is the Liskov Substitution Principle. Since PW and PR are subclasses of P, they can be treated as if they were Ps. However, PWs cannot be treated as PRs, and vice versa.

Harper Shelby
A: 

The second and third would be invalid because it is an implicit upcast -- which is a dangerous thing in C++. This is because the class which you're casting to has more functionality than the class it is being assigned, so unless you explicitly cast it yourself, the C++ compiler will throw an error (at least it should). Of course, this is simplifying things slightly (you can use RTTI for certain things which may relate to what you want to do safely without invoking the wrath of bad object'ness) -- but simplicity is always a good way to approach problems.

Of course, as stated in a few other solutions, you can get around this problem -- but I think before you try to get around the problem, you may want to rethink the design.

nymacro
+1  A: 

To make PW and PR usable via a P you need to use references (or pointers). So you really need t change the interface of C so it returns a reference.

The main problem in the old code was that you were copying a P into a PW or a PR. This is not going to work as the PW and PR potentially have more information than a P and from a type perspective an object of type P is not a PW or a PR. Though PW and PR are both P.

Change the code to this and it will compile: If you want to return different objects derived from a P class at runtime then the class C must potentially be able to store all the different types you expect and be specialized at runtime. So in the class below I allow you to specialize by passing in a pointer to an object that will be returned by reference. To make sure the object is exception safe I have wrapped the pointer in a smart pointer.

class C
{
    public:
        C(std::auto_ptr<P> x):
            p(x)
        {
            if (p.get() == NULL) {throw BadInit;}
        }
        // Return a reference.
        P& GetP() const { return *p; }        
    private:
        // I use auto_ptr just as an example
        // there are many different valid ways to do this.
        // Once the object is correctly initialized p is always valid.
        std::auto_ptr<P> p;
};

// ...
P&  p = c.GetP( );                   // valid
PW& p = dynamic_cast<PW>(c.GetP( )); // valid  Throws exception if not PW
PR& p = dynamic_cast<PR>(c.GetP( )); // valid  Thorws exception if not PR
// ...
Martin York
A: 

This kind of does something sensible, given that everything is being passed by value. Not sure if it's what you were thinking of.

class P
{
public:
    template <typename T>
    operator T() const
    {
        T t;
        static_cast<T&>(t) = *this;
        return t;
    }
};
James Hopkin
+1  A: 
Greg Rogers