Редактировать:
Поскольку отмечено @pasbi, кажется, что у нас уже есть std::experimental::erase_if
, который делает именно это!Он будет объединен с std::
в C ++ 20.
Если вы хотите персонализированную реализацию, читайте дальше.
Вам не нужно специализироваться для конкретных контейнеров.Вместо этого вы можете использовать черты типа и SFINAE для определения «категории» контейнера.
Существует ли (обычно используемый) контейнер, который не подходит ни к одной из категорий?
Я бы сказал, да.Есть std::list
и std::forward_list
, которые имеют .remove_if()
функцию-член, которая должна быть быстрее, чем стирание-удаление.
Таким образом, у нас есть три возможных реализации:
Мыиспользуйте .remove_if()
, если он доступен (как определено std::experimental::is_detected
).
Таким образом, мы обрабатываем std::list
и std::forward_list
.
В противном случае мы используем удаление-удаление, если это возможно.(Это возможно, если элементы контейнера можно назначать перемещением, что можно проверить с помощью std::is_move_assignable
.)
Таким образом, мы обрабатываем все остальные стандартные контейнеры, кроме std::[unordered_]map
и std::[unordered_]set
.(Это то, что вы называете vector
-подобными контейнерами.)
В противном случае мы прибегаем к простому циклу стирания элементов.
Таким образом, мы обрабатываем std::[unordered_]map
и std::[unordered_]set
.
Пример реализации:
#include <algorithm>
#include <iterator>
#include <experimental/type_traits>
#include <utility>
inline auto dummy_predicate = [](auto &&){return false;};
template <typename T> using detect_member_remove_if =
decltype(std::declval<T&>().remove_if(dummy_predicate));
template <typename T, typename F> void remove_if(T &container, F &&func)
{
using element_t = std::remove_reference_t<decltype(*std::begin(container))>;
if constexpr (std::experimental::is_detected_v<detect_member_remove_if, T>)
{
container.remove_if(std::forward<F>(func));
}
else if constexpr (std::is_move_assignable_v<element_t>)
{
auto new_end = std::remove_if(std::begin(container), std::end(container),
std::forward<F>(func));
container.erase(new_end, std::end(container));
}
else
{
auto it = std::begin(container);
while (it != std::end(container))
{
if (func(*it))
it = container.erase(it);
else
it++;
}
}
}
Я бы предпочел что-то без experimental
Вот пользовательская замена для std::experimental::is_detected_v
:
namespace impl
{
template <typename ...P> struct void_impl {using type = void;};
template <typename ...P> using void_t = typename void_impl<P...>::type;
template <typename Dummy, template <typename...> typename A, typename ...B>
struct is_detected : std::false_type {};
template <template <typename...> typename A, typename ...B>
struct is_detected<void_t<A<B...>>, A, B...> : std::true_type {};
}
template <template <typename...> typename A, typename ...B>
inline constexpr bool is_detected_v = impl::is_detected<void, A, B...>::value;
Обратите внимание, что мы не используем C ++ 17 std::void_t
, потому что, насколько я знаю, в Clang он по-прежнему работает неправильно.