如何从任何容器中移除?

时间:2018-12-15 11:36:52

标签: c++ c++11 stl containers

我想从容器中取出某些物品。 问题是,我不知道它是哪种容器。 众所周知,大多数STL算法都不在乎容器:例如find_ifcopy_if等,所有容器类型都可以或多或少地起作用。

但是删除呢? 对于类似vector的容器,有一个remove-erase惯用语,但是它不能应用于例如类似set的容器。 使用模板专门化或重载,我可以专门研究特定的容器,但是在考虑其他容器(unordered_setlist,...)时也无法扩展。

我的问题是: 如何实现一种功能,可以有效地 从任何容器中移除某些物品?首选签名:

template<typename Ts, typename Predicate>
void remove_if(Ts& ts, const Predicate& p);

或更具体的说:如何区分set类容器(快速插入/删除,无自定义顺序)和vector类容器(缓慢插入/删除,自定义顺序)?是否有一个(常用的)容器都不适合这两个类别?

编辑: 我刚刚发现std::experimental::erase_if,它对许多(所有?)容器都有重载。也就是说,只有在不使用std::experimental的情况下,我才会接受解决方案。

2 个答案:

答案 0 :(得分:7)

编辑:

就像@pasbi的noted一样,看来我们已经有了std::experimental::erase_if,它就是这样做的!它将在C ++ 20中合并到std::中。

如果要自定义实现,请先阅读。


您不必专门研究特定的容器。相反,您可以使用特征类型和SFINAE来确定容器的“类别”。

  

是否有一个(常用的)容器都不属于这两个类别?

我会说是的。有std::liststd::forward_list具有成员.remove_if()的成员功能,应该比删除擦除要快。


因此,我们有三种可能的实现方式:

我们使用.remove_if()(如果可用)(由std::experimental::is_detected确定)。
这样,我们就可以处理std::liststd::forward_list

否则,如果可能的话,我们使用擦除删除。 (如果容器元素是可移动分配的,则可以使用std::is_move_assignable进行测试。)
这样,我们可以处理除std::[unordered_]mapstd::[unordered_]set以外的所有其余标准容器。 (这就是您所称的vector类容器。)

否则,我们将对元素进行简单的擦除循环。
这样,我们就可以处理std::[unordered_]mapstd::[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中正确地进行SFINAE。

答案 1 :(得分:2)

std::erasestd::erase_if将成为C ++ 20的一部分。参见P1209

Libc ++(trunk)已经实现了此功能(截至昨天为止:-)),它将成为lang 8.0的一部分。