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?
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.
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() .
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.
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 ;)
however, the conversion (whether explicit with
static_cast
or through theHelper
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.
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
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With