6

If I have a vector of pointers to a parent class, and the vector is initialized by instantiating objects derived from the parent class, then it appears that I can't use the range-based for loop so that I obtain the elements as derived objects. Here's a simple example:

#include <vector>

class Parent {};
class Derived : public Parent {};

int main()
{
    std::vector<Parent*> objects { new Derived(), new Derived() };

    for (Derived* d : objects) {    // ERROR
        // Use d
    }

    return 0;
}

Is there a clean way to do what I want (i.e., loop through the derived objects)? I know can do something like this:

for (Parent* p : objects) {
    Derived* d = static_cast<Derived*>(p);
    // Use d
}

But is this the cleanest way with C++11? What are some other alternatives?

redcurry
  • 2,381
  • 2
  • 24
  • 38
  • 16
    If you are sure all pointers are really `Derived*`, why don't you have `vector` in the first place? And if you aren't sure, what good is your cast (whether explicit or somehow magically performed by the loop)? – Igor Tandetnik Aug 18 '14 at 01:17
  • 1
    I don't think there are any other ways to accomplish what you did. Downcasting is dangerous, and thus you have to do it explicitly to tell the compiler to shut up. – Siyuan Ren Aug 18 '14 at 01:17
  • 1
    Doesn't make sense to do that actually, if you have a collection of base, you need to resolve the downcasting and testing yourself. – Casper Beyer Aug 18 '14 at 01:19
  • 2
    What you really should do is have a virtual function defined in Parent, which you call in your loop. – Neil Kirk Aug 18 '14 at 13:27
  • Igor, my situation is not that simple. The container of pointers to parent objects is a member of an abstract class (call it Model). Derived classes from Model (e.g., Submodel1 and Submodel2) initialize the container with the specific (derived) objects. Within Model, I don't know what the derived objects specifically are, so I treat them as Parent*. But within the Submodels, I do know what they are, and that's why I downcast them. – redcurry Aug 18 '14 at 15:07

3 Answers3

3

You should be able to use dynamic_cast to achieve this:

class Parent {
public:
    virtual ~Parent() { } // make class polymorphic
};
class Derived : public Parent {};

int main()
{
    std::vector<Parent*> objects { new Derived(), new Derived() };

    for (Parent* d : objects) {    // ERROR
        auto temp = dynamic_cast<Derived*>(d);
        if (d) // cast succeeded
        {
            // we found the right class
        }
    }
3

Range iterators is syntactically cleaner and you can't cast within the for loop.

for (Parent* p : objects) {
    Derived* d = static_cast<Derived*>(p);
    // Use d
}

If at all you're down-casting, it should be done within the body. A well designed program doesn't really require down-casting. Be careful!

P.S: Prefer dynamic_cast over static_cast while down-casting. But there's a slight cost of RTTI when you use dynamic_cast. Check - Performance of dynamic_cast?

Community
  • 1
  • 1
sarat
  • 10,512
  • 7
  • 43
  • 74
  • 1
    Actually it should be `static_pointer_cast(p)` because he *should* be using a `shared_ptr` to hold the memory. – David G Aug 18 '14 at 01:31
  • 2
    @0x499602D2: More like a `std::unique_ptr` in his example. `std::sharedptr` is overkill. – Deduplicator Aug 18 '14 at 01:33
  • @Deduplicator I thought about that, but then I thought about if there was an overload of `static_pointer_cast` that accepted a `unique_ptr`, which there doesn't seem to be for some reason. – David G Aug 18 '14 at 01:35
  • "A well designed program doesn't really require down-casting" — I want to know where can I learn how to design programs such a way, please. – sergiol Sep 19 '18 at 11:43
3

The cppreference.com page states that a range-based for loop expression produces a code similar to the following (__range, __begin and __end are for exposition only):

for ( range_declaration : range_expression ) loop_statement

{
    auto && __range = range_expression ; 
    for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) { 
        range_declaration = *__begin; 
        loop_statement 
    } 
}

With that description, I deduce that the __begin and __end must be of the same type, in fact, this isn't your case. A pointer to Parent isn't a pointer to Derived, they must be casted explicitly and the range-based for loop does not perform any cast at all.

In order to achieve what you're looking for, I would create a helper class that performs the cast for me1, for example:

struct Helper
{
    Helper(Parent *p) : obj(p) {}
    Parent *const obj;
    operator Derived *() { return static_cast<Derived *>(obj); }
};

This helper allows us to do the following:

std::vector<Parent*> objects { new Derived(), new Derived() };

for (Helper h : objects) {
    Derived *d = h;
    d->use();
}

We can templatize the helper class in order to use it on other Parent-Derived contexts:

template <typename T> struct THelper
{
    THelper(Parent *p) : obj(p) {}
    Parent *const obj;
    operator T *() { return static_cast<T *>(obj); }
};

...
...

for (THelper<Derived> h : objects) {
    Derived *d = h;
    d->use();
}

for (THelper<Derived2> h : objects) {
    Derived *d = h;
    d->use();
}

But if your objects vector contains mixed types, could be dangerous.

As a improvement, you can replace the static_cast with a dynamic_cast and check for nullptr, but you must be aware of it's overhead.

This isn't a solution that I would use on a project of mine but I hope it looks clean and neat to you ;)

Edit

however, the conversion (whether explicit with static_cast or through the Helper class) is still required as an additional line inside the for loop.

As we talked before, the cast (static or dynamic) is mandatory, but if you're concerned about the neatness of the loop being truncated by the addition of an additional line, overloading the operator -> should do the trick:

struct Helper
{
    Helper(Parent *p) : obj(p) {}
    Parent *const obj;
    operator Derived *() { return static_cast<Derived *>(obj); }
    Derived *operator ->() { return static_cast<Derived *>(obj); }
};

for (Helper h : objects) {
    h->use();
}

Note that if you're using the operator -> you cannot check for nullptr, which would be necessary if your objects vector have mixed derived types.

I've updated the live demoin order to include the new code.


1 Although, as we can read on the comments, there are better ways to achieve your goal.

Community
  • 1
  • 1
PaperBirdMaster
  • 12,806
  • 9
  • 48
  • 94
  • I appreciate your solution; however, the conversion (whether explicit with static_cast or through the Helper class) is still required as an additional line inside the for loop. From people's answers and comments, including your own, it seems that the range-based for loop cannot do any conversions. – redcurry Aug 18 '14 at 15:18
  • I'm accepting this as the answer because you explicitly answered my question in the first part of your post. – redcurry Aug 18 '14 at 15:22