1

Anyone have a better/cleaner way to do this?

class Base {};
class Derived1 : Base {};
class Derived2 : Base {};

//================================

Derived1 var1, var2;
Derived2 var3, var4;

for (auto* var : std::initializer_list<Base*>{&var1, &var2, &var3, &var4}) {
    ...
}

Would like to avoid having to explicitly type the initializer_list and, for bonus points, using references and not having to resort to pointers.

  • What exactly is the purpose of this loop? What are you doing inside the loop that you need this type of list to iterate through? – UnholySheep Aug 10 '23 at 13:10
  • 3
    This feels like an [XY problem](https://en.wikipedia.org/wiki/XY_problem) to me. What is the underlying, actual and real problem that this loop is supposed to solve? Perhaps there are other ways to solve it? – Some programmer dude Aug 10 '23 at 13:11

3 Answers3

3

The only alternate way, but it's probably not what you are looking for:

class Base {};
class Derived1 : public Base {};
class Derived2 : public Base {};

Derived1 var1, var2;
Derived2 var3, var4;

Base* objects[] = { &var1, &var2, &var3, &var4 };

for (auto* var : objects) {
    ...
}
1

You cannot automatically deduce Base. Furthermore there isn't even an implicit conversion of Derived1* or Derived2* to Base*, since you're using a private base class.

I'm assuming you actually wanted public base classes in the following code

class Base {};
class Derived1 : public Base {};
class Derived2 : public Base {};

.

You cannot deduce the common base class of both Derived1 and Derived2, so the type actually needs to be mentioned, if you want to use anything that's iterable, you need to manually specify the type.

However you could create a template function with a parameter pack to either execute a functor for every parameter passed or to create something that is an iterable that returns references to Base when dereferencing the iterator.

In the following I show both options, but for simplicity's sake I go with an std::array of std::reference_wrapper<Base> instead of implementing custom collection that has an iterator returning references to Base.

template<class F, class...Args>
void ForEach(F&& f, Args&&...args)
{
    ((f(std::forward<Args>(args))), ...);
}

void printType(Derived1 const&)
{
    std::cout << "Derived1\n";
}

void printType(Derived2 const&)
{
    std::cout << "Derived2\n";
}

void printType(Base const&)
{
    std::cout << "Base\n";
}

template<class T, class ...Args>
std::array<std::reference_wrapper<T>, sizeof...(Args)> AsIterable(Args& ...args)
{
    return {args...};
}

int main()
{
    Derived1 var1, var2;
    Derived2 var3, var4;

    std::cout << "generic lambda:\n";

    ForEach([](auto& x)
        {
            printType(x);
        }, var1, var2, var3, var4);


    std::cout << "lambda with typed parameter:\n";

    ForEach([](Base& x)
        {
            printType(x);
        }, var1, var2, var3, var4);

    std::cout << "range-based for loop with reference wrapper array:\n";

    for (auto& var : AsIterable<Base>(var1, var2, var3, var4))
    {
        // note: var has type std::reference_wrapper<Base>&, not Base& as mentioned above
        printType(var);
    }

    std::cout << "pointer values(main):\n"
        << &var1 << '\n'
        << &var2 << '\n'
        << &var3 << '\n'
        << &var4 << '\n'
        ;

    std::cout << "pointer values(lambda):\n";
    ForEach([](Base& x)
        {
            std::cout << &x << '\n';
        }, var1, var2, var3, var4);

    std::cout << "pointer values(range-based for):\n";
    for (auto& var : AsIterable<Base>(var1, var2, var3, var4))
    {
        std::cout << &var.get() << '\n';
    }

}
fabian
  • 80,457
  • 12
  • 86
  • 114
0

If you prefer to use references over pointers (as you specified explicitly in your question),
you can use std::reference_wrapper.

In the example below I also used std::array instead of a raw C array as a container:

#include <functional>
#include <array>

class Base { };
class Derived1 : public Base { };
class Derived2 : public Base { };

int main()
{
    Derived1 var1, var2;
    Derived2 var3, var4;

    std::array<std::reference_wrapper<Base>, 4> var_refs = { var1, var2, var3, var4 };
    for (auto & var_ref : var_refs)
    {
        Base& var = var_ref.get();
        // ...
    }
}
wohlstad
  • 12,661
  • 10
  • 26
  • 39
  • 1
    Without the `std::array` one could use `using BaseList = std::reference_wrapper;` and `BaseList var_refs[] = { var1, var2, var3, var4 };`. – Sebastian Aug 10 '23 at 15:37
  • @Sebastian true, but I find both similarly elegant. – wohlstad Aug 10 '23 at 16:23
  • What's the point of being this verbose? How is `reference_wrapper` better than a pointer? – HolyBlackCat Aug 10 '23 at 18:05
  • 1
    @HolyBlackCat I suggeted it because the OP requested it explicitly: _"using references and not having to resort to pointers"_. – wohlstad Aug 11 '23 at 03:58
  • Is it possible to deduct the number of elements 4 with `std::array` and `using`? – Sebastian Aug 11 '23 at 05:28
  • @Sebastian I think CTAD works only if you omit both type and size (which is not possible here). See: https://stackoverflow.com/questions/39933567/deduce-stdarray-size. – wohlstad Aug 11 '23 at 05:44