容器类型的通用`erase_if`方法

时间:2017-09-11 15:37:45

标签: c++ c++14 template-meta-programming

我正在尝试编写一个通用的erase_if方法,可以在任何容器类型上使用它来删除给定谓词的元素。如果容器允许,它应该使用erase-remove-idiom,或者循环容器并调用erase方法。我还想提供容器本身,而不是分别提供beginend迭代器。这应由方法处理。

但是,我无法通过SFINAE来获取元模板以区分这两种情况。我正在尝试检查方法std::remove_if(或std::remove)是否已针对给定类型进行了明确定义,但true和{vector的值均为map {1}}(在这种情况下代码不会编译)或false两者。我对模板元编程很陌生,所以有什么我想念的吗?或许还有另一个更好的解决方案?

以下是我的示例代码:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
#include <type_traits>
#include <vector>


namespace my_std
{
    using std::begin;
    using std::end;

    namespace detail
    {
        template <typename T>
        // this is false for both vector and map
        auto is_remove_compatible(int) -> decltype(std::remove_if(begin(std::declval<T&>()), end(std::declval<T&>()),
                                                                  [](auto&&) { return false; }),
                                                   std::true_type{});

        // this is true for both vector and map
        // auto is_remove_compatible(int)
        //  -> decltype(std::remove(begin(std::declval<T&>()), end(std::declval<T&>()), std::declval<T::value_type&>()),
        //              std::true_type{});

        template <typename T>
        auto is_remove_compatible(...) -> std::false_type;
    }

    template <typename T>
    using is_remove_compatible = decltype(detail::is_remove_compatible<T>(0));

    template <typename T>
    constexpr bool is_remove_compatible_v = is_remove_compatible<T>::value;

    template <typename Cont, typename Pred>
    std::enable_if_t<is_remove_compatible_v<Cont>> erase_if(Cont& container, Pred pred)
    {
        std::cout << "Using erase-remove\n";
        container.erase(std::remove_if(begin(container), end(container), pred), end(container));
    }

    template <typename Cont, typename Pred>
    std::enable_if_t<!is_remove_compatible_v<Cont>> erase_if(Cont& container, Pred pred)
    {
        std::cout << "Using loop\n";
        for (auto it = begin(container); it != end(container);)
        {
            if (pred(*it))
                it = container.erase(it);
            else
                ++it;
        }
    }
}

template <typename T>
std::ostream& operator<<(std::ostream& out, std::vector<T> const& v)
{
    if (!v.empty())
    {
        out << '[';
        std::copy(v.begin(), v.end(), std::ostream_iterator<T>(out, ", "));
        out << "\b\b]";
    }
    return out;
}

template <typename K, typename V>
std::ostream& operator<<(std::ostream& out, std::map<K, V> const& v)
{
    out << '[';
    for (auto const& p : v)
        out << '{' << p.first << ", " << p.second << "}, ";
    out << "\b\b]";
    return out;
}

int main(int argc, int argv[])
{
    auto vp = my_std::is_remove_compatible_v<std::vector<int>>;
    auto mp = my_std::is_remove_compatible_v<std::map<int, int>>;

    std::cout << vp << ' ' << mp << '\n';

    std::vector<int> v = {1, 2, 3, 4, 5};
    auto v2 = v;
    my_std::erase_if(v2, [](auto&& x) { return x % 2 == 0; });

    std::map<int, int> m{{1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}};
    auto m2 = m;
    my_std::erase_if(m2, [](auto&& x) { return x.first % 2 == 0; });

    std::cout << v << " || " << v2 << '\n';
    std::cout << m << " || " << m2 << '\n';

    std::cin.ignore();
}

1 个答案:

答案 0 :(得分:7)

这个想法有两个原因:

auto is_remove_compatible(int) -> decltype(std::remove_if(begin(std::declval<T&>()), end(std::declval<T&>()),
                                                          [](auto&&) { return false; }),
                                           std::true_type{});

首先,您无法在未评估的上下文中拥有lambda,因此代码格式不正确。其次,即使它没有形成(很容易解决),remove_if()对任何事情都不具备SFINAE友好性。如果你将一个不是谓词的东西传递给remove_if,或者传递一个不可修改的迭代器,那么标准不要求它只是一个硬错误。

我现在这样做的方式是通过选择者的习语。基本上,有基于排名顺序的条件sfinae:

template <std::size_t I> struct chooser : chooser<I-1> { };
template <> struct chooser<0> { };

template <class Cont, class Predicate>
void erase_if(Cont& container, Predicate&& predicate) {
    return erase_if_impl(container, std::forward<Predicate>(predicate), chooser<10>{});
}

然后我们按顺序拥有各种erase_if_impl条件:

// for std::list/std::forward_list
template <class Cont, class Predicate>
auto erase_if_impl(Cont& c, Predicate&& predicate, chooser<3> )
    -> decltype(void(c.remove_if(std::forward<Predicate>(predicate))))
{
    c.remove_if(std::forward<Predicate>(predicate));
}

// for the associative containers (set, map, ... )
template <class Cont, class Predicate>
auto erase_if_impl(Cont& c, Predicate&& predicate, chooser<2> )
    -> decltype(void(c.find(std::declval<typename Cont::key_type const&>())))
{
    using std::begin; using std::end;
    for (auto it = begin(c); it != end(c); )
    {
        if (predicate(*it)) {
            it = c.erase(it);
        } else {
            ++it;
        }
    }
}

// for everything else, there's MasterCard
template <class Cont, class Predicate>
void erase_if_impl(Cont& c, Predicate&& predicate, chooser<1> )
{
    using std::begin; using std::end;
    c.erase(std::remove_if(begin(c), end(c), std::forward<Predicate>(predicate)), end(c));
}