Search code examples
c++templatesstandardsc++20

Why do std::forward_list::remove and std::erase<std::forward_list> have different types for value?


std::forward_list has a member function:

size_type remove(const T& value);

and a non-member function std::erase declared as follows:

template<class T, class Alloc, class U>
typename forward_list<T, Alloc>::size_type
    erase(forward_list<T, Alloc>& c, const U& value);

Please note that non-member function std::erase has a different type U for value; while member function remove just use type T for value.

Why doesn't std::erase just use type T for value?

Is there any design rationale behind the inconsistent?


Solution

  • I can see a practical reason. And it has to do with the difficulties imposed by template argument deduction. Imagine this hypothetical function:

    template<typename T>
    void foo(std::forward_list<T> const&, T const&) {}
    

    What do you get for this invocation?

    std::forward_list<double> l;
    foo(l, 1);
    

    The answer is that you get an error in template argument deduction. According to one argument T is double, but according to the other it's int. If I was to write erase, I'd use two different template arguments as well, if only to avoid such issues in innocent code.

    Now remove is not a member template, it is a regular member function of any specialization. So you can write, without problem:

    std::forward_list<double> l;
    // later
    l.remove(1);
    

    1 is an integer, it doesn't match the double that remove expects. However, that is not a problem. Because remove is a regular member function of a concrete specialization, and an implicit conversion is possible.