Remove_if with vector containing variants
Asked Answered
M

2

5

I have two different objects:

struct TypeA {
    std::size_t no;
    std::string data;
    std::string data2;
};

struct TypeB {
    std::size_t no;
    std::string data;
    std::string data2;
    std::string data3;
};

They are stored in a std::vector with std::variant

std::vector<std::variant< TypeA, TypeB>> ab;

Now i want to remove all elements were the member no = 0.

Without the std::variant with the vector containing only TypeA I would do it like this:

ab.erase(std::remove_if(ab.begin(), ab.end(),
    [](const TypeA& a) { return a.no == 0; }), ab.end());

But how to incorporate the std::variant ? I tried to come up with something with std::visit but i cannot ad it in the predicate of std::remove_if or can I?

Mannino answered 31/1, 2019 at 21:12 Comment(0)
T
11

Yes, std::visit can help. The functor passed to visit just needs to be able to accept each type of the variant, and the easiest way to do that is with a generic lambda:

ab.erase(
    std::remove_if(
        ab.begin(),
        ab.end(),
        [](const auto &v) {
            return std::visit(
                [](const auto &obj) { return obj.no == 0; },
                v);
    }),
    ab.end());

Here the type of v for the outer lambda is always used as const std::variant<TypeA, TypeB>&, and auto is just more convenient than typing out std::variant<TypeA, TypeB>. But for the inner lambda, it's important that the lambda is generic, because visit will instantiate its template operator() with both TypeA and TypeB.

Timmons answered 31/1, 2019 at 21:32 Comment(0)
C
3

If you want to access the "same" data member of different types, then these types need to be subclasses of a common polymorphic base class defining this data member.

In your case, however, where TypeA and TypeB are not related, you'll have to make a type-safe access to the respective data member. The solution provided by @aschepler shows this in a generic way using std::visit functor; the following solution is without std::visit (hence not that elegant, but still working):

ab.erase(std::remove_if(ab.begin(), ab.end(),
    [](const std::variant< TypeA, TypeB>& v) { 
      int no;
      if (v.index()==0) {
         no = std::get<0>(v).no;
      } else {
         no = std::get<1>(v).no;
      }
      return no==0;
    }), ab.end());
Collette answered 31/1, 2019 at 21:45 Comment(1)
This is just asking for trouble. What happens when you add a 3rd type to the variant? Do you remember that you need to update this? What if the type you happen to be looking for isn't cheap-to-copy? Or copyable at all? Or default constructible?Cogen

© 2022 - 2024 — McMap. All rights reserved.