Topic: Iterator reference types, STL algorithms and tr1::bind


Author: "Richard Smith" <richard@ex-parrot.com>
Date: Tue, 10 Apr 2007 11:59:57 CST
Raw View
Because it is not possible to 'perfectly' forward function arguments
in the current standard, tr1::bind is specified to forward its
arguments by non-const reference with the result that rvalues
arguments will not necessarily work.  (Implementations are permitted
to make them work, though; and in C++09 rvalue references will
hopefully make it all 'just' work.)

One of the main places where tr1::bind is likely to be used is as an
argument to an STL algorithm; std::for_each is the archetypal example,
and this is typically implemented:

  template<class InputIterator, class Function>
  Function for_each(InputIterator first, InputIterator last, Function
f) {
    for ( ; first != last; ++first )
      f( *first );
    return f;
  }

Clearly if Function is a tr1::bind expression (or something more
exotic such as a boost::lambda expression), and if InputIterator has
an operator* that returns by non-const value, this will hit the
problem of rvalue forwarding.  (And it is then implementation defined
what the compiler does.)

A minor modification to one of the examples in the
boost::transform_iterators documentation gives an example of this
breaking:

  int x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
  const int N = sizeof(x)/sizeof(int);

  std::for_each
  ( boost::make_transform_iterator(x, boost::bind1st(std::plus<int>(),
4)),
    boost::make_transform_iterator(x + N,
boost::bind1st(std::plus<int>(), 4)),
    std::cout << boost::lambda::_1 << " " );

Although fixing this is clearly a QoI issue, are there any reasons why
an STL implementation might not want to implement for_each as follows?

  template<class InputIterator, class Function>
  Function for_each(InputIterator first, InputIterator last, Function
f) {
    for ( ; first != last; ++first ) {
      Ref /*see below*/  val(*first);
      f( val );
    }
    return f;
  }

This just leaves the question of what 'Ref' should be.  The obvious
choice is to simply make Ref be
std::iterator_traits<InputIterator>::reference.  This is not
desirable, though, as, in the case where operator* returns by value
and reference type is the same as value_type, it does an unnecessary
copy of the value which might be expensive.

The next obvious choice is for Ref to be
std::iterator_traits<>::reference if that is a raw reference, and
value_type const& otherwise.  That way, if operator* returns by value,
the returned rvalue's lifetime is extended.  But this is a problem if
the iterator has an operator* that returns a proxy (much like some
operator-> proxies):

  struct reference {
    explicit proxy( T const& t ) : t(t) {}
    operator T const&() const { return t; }
    T t;
  };
  reference operator*() const;

In such a case, binding

  T const& val( *first );
  f( *first );

will result in undefined behaviour (assuming 'f' uses its argument) as
the T will already have been destroyed.

. Which leads to a third possibility:  if
iterator_traits<>::reference is the same type (modulo top level
constness) as iterator_traits<>::value_type, then Ref is value_type
const&; otherwise Ref is iterator_traits<>::reference.  The only thing
that this then requires is that reference is copy constructible, which
is must be if it is returned from a function (viz., operator*).

My question is, is this legal?  It seems to have no major
disadvantages (the only one I can see is that it forces an additional
copy construction in the arcane case of operator* returning by proxy),
but it increases the usability of tr1::bind (and also improves the
quality of error messages in at least one compiler).  But I've never
seen this done.  So are there any other corner cases that this might
break?

--
Richard Smith

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]