561. inserter overly generic

Section: 24.5.2.4.3 [inserter] Status: CD1 Submitter: Howard Hinnant Opened: 2006-02-21 Last modified: 2016-01-28

Priority: Not Prioritized

View all issues with CD1 status.

Discussion:

The declaration of std::inserter is:

template <class Container, class Iterator>
insert_iterator<Container>
inserter(Container& x, Iterator i);

The template parameter Iterator in this function is completely unrelated to the template parameter Container when it doesn't need to be. This causes the code to be overly generic. That is, any type at all can be deduced as Iterator, whether or not it makes sense. Now the same is true of Container. However, for every free (unconstrained) template parameter one has in a signature, the opportunity for a mistaken binding grows geometrically.

It would be much better if inserter had the following signature instead:

template <class Container>
insert_iterator<Container>
inserter(Container& x, typename Container::iterator i);

Now there is only one free template parameter. And the second argument to inserter must be implicitly convertible to the container's iterator, else the call will not be a viable overload (allowing other functions in the overload set to take precedence). Furthermore, the first parameter must have a nested type named iterator, or again the binding to std::inserter is not viable. Contrast this with the current situation where any type can bind to Container or Iterator and those types need not be anything closely related to containers or iterators.

This can adversely impact well written code. Consider:

#include <iterator>
#include <string>

namespace my
{

template <class String>
struct my_type {};

struct my_container
{
template <class String>
void push_back(const my_type<String>&);
};

template <class String>
void inserter(const my_type<String>& m, my_container& c) {c.push_back(m);}

}  // my

int main()
{
    my::my_container c;
    my::my_type<std::string> m;
    inserter(m, c);
}

Today this code fails because the call to inserter binds to std::inserter instead of to my::inserter. However with the proposed change std::inserter will no longer be a viable function which leaves only my::inserter in the overload resolution set. Everything works as the client intends.

To make matters a little more insidious, the above example works today if you simply change the first argument to an rvalue:

    inserter(my::my_type(), c);

It will also work if instantiated with some string type other than std::string (or any other std type). It will also work if <iterator> happens to not get included.

And it will fail again for such inocuous reaons as my_type or my_container privately deriving from any std type.

It seems unfortunate that such simple changes in the client's code can result in such radically differing behavior.

Proposed resolution:

Change 24.2:

24.2 Header <iterator> synopsis

...
template <class Container, class Iterator>
   insert_iterator<Container> inserter(Container& x, Iterator typename Container::iterator i);
...

Change 24.4.2.5:

24.4.2.5 Class template insert_iterator

...
template <class Container, class Iterator>
   insert_iterator<Container> inserter(Container& x, Iterator typename Container::iterator i);
...

Change 24.4.2.6.5:

24.4.2.6.5 inserter

template <class Container, class Inserter>
   insert_iterator<Container> inserter(Container& x, Inserter typename Container::iterator i);

-1- Returns: insert_iterator<Container>(x,typename Container::iterator(i)).

[ Kona (2007): This issue will probably be addressed as a part of the concepts overhaul of the library anyway, but the proposed resolution is correct in the absence of concepts. Proposed Disposition: Ready ]