Your problem is that you are doing the erase-remove idiom inline. It is very error prone.
template<class C, class F> void erase_remove_if( C&& c, F&& f ) { using std::begin; using std::end; auto it = std::remove_if( begin(c), end(c), std::forward<F>(f) ); c.erase( it, end(c) ); }
this little helper function does the error prone part of erase remove in isolation from other noise.
Then:
a.erase( (std::remove_if(a.begin(), a.end(), [](const int& x){ return x == 2; }), a.end()));
becomes
erase_remove_if( a, [](const int& x){ return x == 2; } );
and suddenly your code works.
Now the proximate cause was you had a typo:
a.erase( ( std::remove_if( a.begin(), a.end(), [](const int& x){ return x == 2; } ), a.end() ) );
here I have expanded the line's structure. You can see from the above that you only passed one argument to erase; namely a.end(), because you passed it ( some remove expression, a.end() ) in brackets. This invoked the comma operator: so it ran the remove expression (moving the element 2 to the end), then discarded the returned iterator and evaluated to a.end().
We then passed a.end() to erase, which is not a valid iterator to pass to erase. So your program is ill-formed, and UB results.
That is only the proximate cause. There are many mistakes you can easily make when manually doing erase-remove; the code is fragile and full of repetition.
DRY is the principle that you want a single point of customization, and you don't want to repeat things that don't need repeating. erase_remove_if is my attempt to apply DRY to avoid exactly this kind of error.