1334. Insert iterators are broken for some proxy containers compared to C++03

Section: 24.5.2.2.2 [back.insert.iter.ops], 24.5.2.3.2 [front.insert.iter.ops], 24.5.2.4.2 [insert.iter.ops] Status: C++11 Submitter: Daniel Krügler Opened: 2010-03-28 Last modified: 2023-02-07

Priority: Not Prioritized

View all other issues in [back.insert.iter.ops].

View all issues with C++11 status.

Discussion:

In C++03 this was valid code:

#include <vector>
#include <iterator>

int main() {
  typedef std::vector<bool> Cont;
  Cont c;
  std::back_insert_iterator<Cont> it = std::back_inserter(c);
  *it = true;
}

In C++0x this code does no longer compile because of an ambiguity error for this operator= overload pair:

back_insert_iterator<Container>&
operator=(typename Container::const_reference value);

back_insert_iterator<Container>&
operator=(typename Container::value_type&& value);

This is so, because for proxy-containers like std::vector<bool> the const_reference usually is a non-reference type and in this case it's identical to Container::value_type, thus forming the ambiguous overload pair

back_insert_iterator<Container>&
operator=(bool value);

back_insert_iterator<Container>&
operator=(bool&& value);

The same problem exists for std::back_insert_iterator, std::front_insert_iterator, and std::insert_iterator.

One possible fix would be to require that const_reference of a proxy container must not be the same as the value_type, but this would break earlier valid code. The alternative would be to change the first signature to

back_insert_iterator<Container>&
operator=(const typename Container::const_reference& value);

This would have the effect that this signature always expects an lvalue or rvalue, but it would not create an ambiguity relative to the second form with rvalue-references. [For all non-proxy containers the signature will be the same as before due to reference-collapsing and const folding rules]

[ Post-Rapperswil ]

This problem is not restricted to the unspeakable vector<bool>, but is already existing for other proxy containers like gcc's rope class. The following code does no longer work ([Bug libstdc++/44963]):

#include <iostream>
#include <ext/rope>

using namespace std;

int main()
{
     __gnu_cxx::crope line("test");
     auto ii(back_inserter(line));

     *ii++ = 'm'; // #1
     *ii++ = 'e'; // #2

     cout << line << endl;
}

Both lines marked with #1 and #2 issue now an error because the library has properly implemented the current wording state (Thanks to Paolo Calini for making me aware of this real-life example).

The following P/R is a revision of the orignal P/R and was initially suggested by Howard Hinnant. Paolo verified that the approach works in gcc.

Moved to Tentatively Ready with revised wording after 6 positive votes on c++std-lib.

[ Adopted at 2010-11 Batavia ]

Proposed resolution:

The wording refers to N3126.

  1. Change [back.insert.iterator], class back_insert_iterator synopsis as indicated:
    template <class Container>
    class back_insert_iterator :
     public iterator<output_iterator_tag,void,void,void,void> {
    protected:
     Container* container;
    public:
     [..]
     back_insert_iterator<Container>&
       operator=(const typename Container::const_referencevalue_type& value);
     back_insert_iterator<Container>&
       operator=(typename Container::value_type&& value);
     [..]
    };
    
  2. Change [back.insert.iter.op=] before p. 1 as indicated:
    back_insert_iterator<Container>&
       operator=(const typename Container::const_referencevalue_type& value);
    

    1 Effects: container->push_back(value);
    2 Returns: *this.

  3. Change [front.insert.iterator], class front_insert_iterator synposis as indicated:
    template <class Container>
    class front_insert_iterator :
     public iterator<output_iterator_tag,void,void,void,void> {
    protected:
     Container* container;
    public:
     [..]
     front_insert_iterator<Container>&
       operator=(const typename Container::const_referencevalue_type& value);
     front_insert_iterator<Container>&
       operator=(typename Container::value_type&& value);
     [..]
    };
    
  4. Change [front.insert.iter.op=] before p.1 as indicated:
    front_insert_iterator<Container>&
       operator=(const typename Container::const_referencevalue_type& value);
    

    1 Effects: container->push_front(value);
    2 Returns: *this.

  5. Change [insert.iterator], class insert_iterator synopsis as indicated:
    template <class Container>
       class insert_iterator :
         public iterator<output_iterator_tag,void,void,void,void> {
       protected:
         Container* container;
         typename Container::iterator iter;
       public:
         [..]
         insert_iterator<Container>&
           operator=(const typename Container::const_referencevalue_type& value);
         insert_iterator<Container>&
           operator=(typename Container::value_type&& value);
         [..]
       };
    
  6. Change [insert.iter.op=] before p. 1 as indicated:
    insert_iterator<Container>&
        operator=(const typename Container::const_referencevalue_type& value);
    

    1 Effects:

      iter = container->insert(iter, value);
      ++iter;
    

    2 Returns: *this.