Search code examples
c++templatesstlc++17sfinae

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


I have two implementations of the erase_all_iffunction template.

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;
}

The first works only for containers like std::vector because it requires a random access iterator and the second one for all containers, but it is more inefficient for continuous containers and it is appropriate for node-based containers. How to disambiguate the two versions? The C++ standard I'm currently using is C++17.


Solution

  • You don't need SFINAE here. I would combine the two functions into one, and do

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

    Or you can use tag dispatch: (note that iterator categories inherit from each other, so this handles fallback between categories nicely)

    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));
    }