Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove_if with vector containing variants

Tags:

c++

c++17

variant

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?

like image 714
Sandro4912 Avatar asked Jan 31 '19 21:01

Sandro4912


People also ask

How to remove std in C++?

remove() is an inbuilt function in C++ STL which is declared in header file. remove() is used to remove any specific value/element from the list container. It takes the value which is passed as a parameter and removes all the elements with that value from the list container.

How do I remove something from a vector in C++?

The C++ vector has many member functions. Two of these member functions are erase() and pop_back(). pop_back() removes the last element from the vector. In order to remove all the elements from the vector, using pop_back(), the pop_back() function has to be repeated the number of times there are elements.

How do you remove negative elements from a vector in C++?

The recommended way is to use the remove_if and erase pattern. Note that std::vector has two versions of erase(). The one used in this thread takes two arguments and removes a range of elements.


2 Answers

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.

like image 138
aschepler Avatar answered Nov 02 '22 23:11

aschepler


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());
like image 42
Stephan Lechner Avatar answered Nov 03 '22 01:11

Stephan Lechner