如何消除“erase_all_if”函数模板的重载歧义?

How to disambiguate the overloads of the `erase_all_if` function template?

提问人:bobeff 提问时间:5/23/2023 最后编辑:bobeff 更新时间:5/23/2023 访问量:74

问:

我有两个函数模板的实现。erase_all_if

template <typename Container, typename Pred>
typename Container::size_type erase_all_if(Container& c, Pred&& pred)
{
    auto newend = std::remove_if(c.begin(), c.end(), std::forward<Pred>(pred));
    auto ret = c.end() - newend;
    c.erase(newend, c.end());
    return ret;
}

template <typename Container, typename Pred>
auto erase_all_if(Container& c, Pred&& pred) {
    typename Container::size_type removed = 0;
    for (auto it = c.begin(); it != c.end();) {
        if (pred(*it)) {
            it = c.erase(it);
            ++removed;
        }
        else {
            ++it;
        }
    }
    return removed;
}

第一种仅适用于容器,因为它需要一个随机访问迭代器,第二个适用于所有容器,但对于连续容器来说效率较低,并且适用于基于节点的容器。如何消除这两个版本的歧义?我目前使用的 C++ 标准是 C++17。std::vector

C++ 模板 STL C++17 SFINAE

评论

0赞 463035818_is_not_an_ai 5/23/2023
这两个函数中只有一个就足够了,但请不要从代码中删除包含。实例化它们的示例也有助于使某些东西正常工作

答:

5赞 HolyBlackCat 5/23/2023 #1

您在这里不需要 SFINAE。我会将这两个功能合二为一,然后做

if constexpr (std::is_base_of_v<std::random_access_iterator_tag,
    typename std::iterator_traits<decltype(c.begin())>::iterator_category>)
{
    // ...
}
else
{
    // ...
}

或者你可以使用标签调度:(请注意,迭代器类别是相互继承的,所以这可以很好地处理类别之间的回退)

template <typename Container, typename Pred>
typename Container::size_type erase_all_if_low(std::random_access_iterator_tag, Container& c, Pred&& pred)
{
    // ...
}

template <typename Container, typename Pred>
typename Container::size_type erase_all_if_low(std::forward_iterator_tag, Container& c, Pred&& pred)
{
    // ...
}

template <typename Container, typename Pred>
auto erase_all_if(Container& c, Pred &&pred)
{
    using category = typename std::iterator_traits<decltype(c.begin())>::iterator_category;
    return erase_all_if_low(category{}, c, std::forward<Pred>(pred));
}
6赞 fabian 5/23/2023 #2

此处不需要 SFINAE。您可以在此处使用“添加帮助程序”模板,该模板接收中间器类别作为附加参数。您甚至可以通过一些调整使用 C++11 来完成这项工作。

// helper templates with overloads taking a parameter indicating info about the iterator category
namespace impl
{

    template<class T>
    constexpr typename std::iterator_traits<decltype(std::declval<T>().begin())>::iterator_category ContainerIteratorCategory_v {};

    template <typename Container, typename Pred>
    typename Container::size_type erase_all_if(std::random_access_iterator_tag, Container& c, Pred&& pred)
    {
        auto newend = std::remove_if(c.begin(), c.end(), std::forward<Pred>(pred));
        auto ret = c.end() - newend;
        c.erase(newend, c.end());
        return ret;
    }

    template <typename Container, typename Pred>
    auto erase_all_if(std::input_iterator_tag, Container& c, Pred&& pred) {
        typename Container::size_type removed = 0;
        for (auto it = c.begin(); it != c.end();) {
            if (pred(*it)) {
                it = c.erase(it);
                ++removed;
            }
            else {
                ++it;
            }
        }
        return removed;
    }
}

template <typename Container, typename Pred>
auto erase_all_if(Container& c, Pred&& pred) {
    return impl::erase_all_if(impl::ContainerIteratorCategory_v<Container>, c, std::forward<Pred>(pred));
}

template<class Container>
void Print(Container const& c)
{
    for (auto& e : c)
    {
        std::cout << e << '\n';
    }
}

int main()
{
    {
        std::vector<int> v{ 1, 2, 3, 4, 5 };
        std::cout << "std::vector<int>: " << erase_all_if(v, [](int i) { return (i % 2) == 0; }) << '\n';
        Print(v);
    }
    {
        std::list<int> l{6, 7, 8, 9, 10};
        std::cout << "std::list<int>: " << erase_all_if(l, [](int i) { return (i % 2) == 0; }) << '\n';
        Print(l);
    }
}