Fixed accessing members of the dereferenced value after iterator post-increment.

The recent commit 5777e9944bf275e7e19e72e88819ec06fea670c3 broke code such as
(*it++).foo(), where the result of dereferencing would be convertible to
the value type but did not provide the members of the value type. To mitigate
this, return a reference to the value instead of a proxy object. This will only
work for non-writable iterators (and it didn't work for writable iterators
before either) because in that case a proxy is needed to be able to intercept
operator=.

Also fix a similar issue with (it++)->foo() by adding operator-> overloads
to the post-increment result proxies.

Added tests for the fixes.
This commit is contained in:
Andrey Semashev 2022-11-18 00:39:26 +03:00
parent 0a95636faf
commit 7c9b4296a1
2 changed files with 66 additions and 44 deletions

View File

@ -150,54 +150,25 @@ namespace iterators {
// value must be read and stored away before the increment occurs
// so that *a++ yields the originally referenced element and not
// the next one.
template <class Value>
class postfix_increment_dereference_proxy
template <class Iterator>
class postfix_increment_proxy
{
typedef Value value_type;
public:
#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
template<typename OtherValue>
explicit postfix_increment_dereference_proxy(OtherValue&& x)
: stored_value(static_cast< OtherValue&& >(x))
{}
#else
explicit postfix_increment_dereference_proxy(value_type const& x)
: stored_value(x)
{}
typedef typename iterator_value<Iterator>::type value_type;
explicit postfix_increment_dereference_proxy(value_type& x)
: stored_value(x)
public:
explicit postfix_increment_proxy(Iterator const& x)
: stored_iterator(x)
, stored_value(*x)
{}
#endif
// Returning a mutable reference allows nonsense like
// (*r++).mutate(), but it imposes fewer assumptions about the
// behavior of the value_type. In particular, recall that
// (*r).mutate() is legal if operator* returns by value.
// Provides readability of *r++
operator value_type&() const
value_type& operator*() const
{
return this->stored_value;
}
private:
mutable value_type stored_value;
};
template <class Iterator>
class postfix_increment_proxy
{
typedef typename iterator_value<Iterator>::type value_type;
public:
explicit postfix_increment_proxy(Iterator const& x)
: stored_iterator(x)
, dereference_proxy(*x)
{}
postfix_increment_dereference_proxy<value_type> const&
operator*() const
{
return dereference_proxy;
return stored_value;
}
// Provides X(r++)
@ -206,9 +177,15 @@ namespace iterators {
return stored_iterator;
}
// Provides (r++)->foo()
value_type* operator->() const
{
return boost::addressof(stored_value);
}
private:
Iterator stored_iterator;
postfix_increment_dereference_proxy<value_type> dereference_proxy;
mutable value_type stored_value;
};
@ -304,6 +281,8 @@ namespace iterators {
template <class Iterator>
class writable_postfix_increment_proxy
{
typedef typename iterator_value<Iterator>::type value_type;
public:
explicit writable_postfix_increment_proxy(Iterator const& x)
: dereference_proxy(x)
@ -321,6 +300,12 @@ namespace iterators {
return dereference_proxy.stored_iterator;
}
// Provides (r++)->foo()
value_type* operator->() const
{
return boost::addressof(dereference_proxy.stored_value);
}
private:
writable_postfix_increment_dereference_proxy<Iterator> dereference_proxy;
};

View File

@ -10,7 +10,7 @@
#include <boost/call_traits.hpp>
#include <boost/polymorphic_cast.hpp>
#include <boost/type_traits/is_convertible.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/core/enable_if.hpp>
// This is a really, really limited test so far. All we're doing
// right now is checking that the postfix++ proxy for single-pass
@ -63,7 +63,23 @@ struct proxy
struct value
{
void mutator() {} // non-const member function
int increment_count;
int private_mutator_count;
int& shared_mutator_count;
explicit value(int& shared_mutator_count) :
increment_count(0),
private_mutator_count(0),
shared_mutator_count(shared_mutator_count)
{
}
// non-const member function
void mutator()
{
++private_mutator_count;
++shared_mutator_count;
}
};
struct input_iter
@ -75,21 +91,25 @@ struct input_iter
>
{
public:
input_iter() {}
explicit input_iter(value& val) : state(&val) {}
void increment()
{
++(state->increment_count);
}
value
dereference() const
{
return value();
return *state;
}
bool equal(input_iter const&) const
{
return false;
}
private:
value* state;
};
template <class T>
@ -198,13 +218,30 @@ int main()
{
// test for a fix to http://tinyurl.com/zuohe
// These two lines should be equivalent (and both compile)
input_iter p;
int shared_mutator_count = 0;
value val(shared_mutator_count);
input_iter p(val);
(*p).mutator();
p->mutator();
BOOST_TEST_EQ(val.increment_count, 0);
BOOST_TEST_EQ(val.private_mutator_count, 0); // mutator() should be invoked on an object returned by value
BOOST_TEST_EQ(shared_mutator_count, 2);
same_type<input_iter::pointer>(p.operator->());
}
{
// Test that accessing dereferenced value of a post-incremented iterator works
int shared_mutator_count = 0;
value val(shared_mutator_count);
input_iter p(val);
(*p++).mutator();
(p++)->mutator();
BOOST_TEST_EQ(val.increment_count, 2);
BOOST_TEST_EQ(val.private_mutator_count, 0); // mutator() should be invoked on an object returned by value
BOOST_TEST_EQ(shared_mutator_count, 2);
}
{
int x = 0;
iterator_with_proxy_reference i(x);