Skip to content

Latest commit

 

History

History
235 lines (155 loc) · 5.57 KB

iter-swap.md

File metadata and controls

235 lines (155 loc) · 5.57 KB
std::ranges::iter_swap std::ranges::iter_move
template <class I>
void SwapIters(I lhs, I rhs)
{
	using std::swap;
	swap(*lhs, *rhs);
}

Proxy reference type could be pair<T&,T&>, so this just swapped two rvalues.

I could use std::iter_swap for this, but it actually does the same thing. It can be used to avoid this pattern, but it has the same problem with proxy iterators.

std::ranges::iter_swap

  • Improvement upon std::iter_swap from C++98.
  • Intentionally designed customization point for proxy iterators.
  • Used by all std::ranges algorithms that swap stuff.
If your generic code needs to swap elements in a container, use this.
You usually won’t have to customize this; calls iter_move by default.
template <class I>
std::iter_value_t<I> Extract(I iter)
{
	return std::move(*iter);
}

Proxy reference type could be pair<T&,T&>, so this moved an rvalue.

std::ranges::iter_move

  • Didn’t exist before, even after std::move came along.
  • Another customization point for proxy iterators.
  • Used by all std::ranges algorithms that move values.
If std::move won’t work for your iterators, you have to customize this.
Customization gives you semantically correct iter_swap for free.

Another benefit of sentinels:
Won't work with the old algorithms.*

But how does one "customize" std::ranges::iter_move?

cppreference: iter_move

Obtains an rvalue reference or a prvalue temporary from a given iterator.

A call to ranges::iter_move is expression-equivalent to:

  • iter_move(std::forward<T>(t)), if std::remove_cvref_t<T> is a class or enumeration type and the expression is well-formed in unevaluated context, where the overload resolution is performed with the following candidates:
    • void iter_move();
    • any declarations of iter_move found by argument-dependent lookup.
  • otherwise, std::move(*std::forward<T>(t)) if *std::forward<T>(t) is well-formed and is an lvalue,
  • otherwise, *std::forward<T>(t) if *std::forward<T>(t) is well-formed and is an rvalue.

In all other cases, a call to ranges::iter_move is ill-formed, which can result in substitution failure when ranges::iter_move(e) appears in the immediate context of a template instantiation.

If ranges::iter_move(e) is not equal to *e, the program is ill-formed, no diagnostic required.

You can look up the overload resolution rules for iter_move...

Simplified: iter_move

  • Define a function your_iter_namespace::iter_move that accepts your iterator.
  • If you don’t, it'll just use std::move(*i).
class inner_iterator
{
private:
	using base_value_type = std::ranges::range_value_t<TBase>;
	using base_reference = std::ranges::range_reference_t<TBase>;
 
public:
	using value_type = std::pair<base_value_type, base_value_type>;
	using reference = std::pair<base_reference, base_reference>;
 
	friend constexpr auto iter_move(inner_iterator i)
    {
		using base_rref = std::ranges::range_rvalue_reference_t<TBase>;
		return std::pair<base_rref, base_rref> {
			std::ranges::iter_move(i._current_outer),
			std::ranges::iter_move(i._current_inner)
		};
    }
};
class inner_iterator
{
private:
	using base_value_type = std::ranges::range_value_t<TBase>;
	using base_reference = std::ranges::range_reference_t<TBase>;
 
public:
	using value_type = std::pair<base_value_type, base_value_type>;
	using reference = std::pair<base_reference, base_reference>;
 
	friend constexpr auto iter_move(inner_iterator i)
    {
		return std::pair {
			std::ranges::iter_move(i._current_outer),
			std::ranges::iter_move(i._current_inner)
		};
    }
};

Views that expose elements multiple times can't use iter_move.