dxx-rebirth/common/main/selfiter.h
Kp bde498894a Disallow operator=(T &&) && in valptridx
GCC std::remove_if overwrites removed elements using:

	*dstiter = move(*srciter);

This is fine for normal containers, but produces incorrect results when
*dstiter returns a proxy object instead of a reference.  In that case,
the proxy object is move-assigned from the source, then goes out of
scope.  If the move assignment did not write to underlying storage, as
valptridx proxy objects do not, then incorrect results occur.  This
broke ActiveDoor handling (fixed in 4a01fab66d98[1]) and has been a trap
waiting to recur.  Apply reference-qualifiers to valptridx objects so
that move-assignment requires an lvalue for the left-hand side.  This
permits normal use of move-assignment, but forces a compile error if
std::remove_if or similar are used on valptridx proxy objects.

[1]: 4a01fab66d
2018-06-08 04:04:05 +00:00

87 lines
2.6 KiB
C++

/*
* This file is part of the DXX-Rebirth project <http://www.dxx-rebirth.com/>.
* It is copyright by its individual contributors, as recorded in the
* project's Git history. See COPYING.txt at the top level for license
* terms and a link to the Git history.
*/
#pragma once
#include <iterator>
#include <utility>
template <typename T>
class self_return_iterator :
public std::iterator<std::forward_iterator_tag, T>,
T
{
public:
self_return_iterator(T &&i) :
T(std::move(i))
{
}
T base() const
{
return *this;
}
T operator*() const
{
/* This static_assert is eager: it will reject a type T that
* would be dangerous if used in the affected algorithms,
* regardless of whether the program attempts such a use. This
* is acceptable since the modification to fix this assertion
* should not break any intended uses of the type. To pass the
* assertion, the type T must define:
T &operator=(T &&) && = delete;
* If normal move assignment is desired, also define:
T &operator=(T &&) & = default;
*/
static_assert(!std::is_assignable<T &&, T &&>::value, "Accessibility of `T::operator=(T &&) &&` permits generation of incorrect code when passing self_return_iterator<T> to some algorithms. Explicitly delete `T::operator=(T &&) &&` to inhibit this assertion.");
return *this;
}
/* Some STL algorithms require:
*
* !!std::is_same<decltype(iter), decltype(++iter)>::value
*
* If this requirement is not met, template argument deduction
* fails when the algorithm calls a helper function.
*
* If not for this requirement, `using T::operator++` would have
* been sufficient here.
*/
self_return_iterator &operator++()
{
/* Use a static_cast instead of ignoring the return value and
* returning `*this`, to encourage the compiler to implement
* this as a tail-call when
*
* offsetof(self_return_iterator, T) == 0
*/
return static_cast<self_return_iterator &>(this->T::operator++());
}
/* operator++(int) is currently unused, but is required to satisfy
* the concept check on forward iterator.
*/
self_return_iterator operator++(int)
{
auto result = *this;
this->T::operator++();
return result;
}
/* Since `T` is inherited privately, the base class `operator==` and
* `operator!=` cannot implicitly convert `rhs` to `T`. Define
* comparison operators to perform the conversion explicitly.
*/
bool operator==(const self_return_iterator &rhs) const
{
return this->T::operator==(static_cast<const T &>(rhs));
}
bool operator!=(const self_return_iterator &rhs) const
{
return this->T::operator!=(static_cast<const T &>(rhs));
}
};