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,Iteratortypename 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,Iteratortypename 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,Insertertypename 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 ]