Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 range-based for loop on derived objects

Tags:

c++

c++11

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?

like image 325
redcurry Avatar asked Aug 18 '14 01:08

redcurry


People also ask

Does C have range-based for loops?

Range-based for loop in C++ Range-based for loop in C++ is added since C++ 11. It executes a for loop over a range. Used as a more readable equivalent to the traditional for loop operating over a range of values, such as all elements in a container.

Can vectors in C++ be iterated using range-based for loops?

Use the range-based for statement to construct loops that must execute through a range, which is defined as anything that you can iterate through—for example, std::vector , or any other C++ Standard Library sequence whose range is defined by a begin() and end() .

What is range loop?

To loop through a set of code a specified number of times, we can use the range() function, The range() function returns a sequence of numbers, starting from 0 by default, and increments by 1 (by default), and ends at a specified number.


2 Answers

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.

like image 131
PaperBirdMaster Avatar answered Oct 08 '22 18:10

PaperBirdMaster


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
        }
    }
like image 44
user3951088 Avatar answered Oct 08 '22 18:10

user3951088