Range based for loop using an index

The Partridge Family were neither partridges nor a family. Discuss.
albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Range based for loop using an index

Post by albinopapa » November 1st, 2019, 7:30 pm

Code: Select all

#pragma once

#include <cassert>
#include <cstdint>
#include <limits>

using offset_t = long long;

class IndexRange
{
public:
	class iterator
	{
	public:
		constexpr iterator( std::int32_t _value, std::int32_t _step )noexcept
			:
			m_value( _value ),
			m_step( _step )
		{}
		constexpr void _validate( offset_t _offset )const noexcept {
			constexpr auto iMax =
				offset_t( std::numeric_limits<std::int32_t>::max() );
			constexpr auto iMin =
				offset_t( std::numeric_limits<std::int32_t>::min() );

			// If _offset is equal to iMax or iMin then _offset is not valid
			assert( _offset < iMax );
			assert( _offset > iMin );

			// If iMax / _offset is equal to 0, m_value cannot be incremented
			if( _offset != 0 )
				assert( std::abs( iMax / ( _offset * m_step ) ) > 0 );

			// If iMin / _offset is equal to 0, m_value cannot be decremented
			if( _offset != 0 )
				assert( std::abs( iMin / ( _offset * m_step ) ) > 0 );

			const auto steppedOffset = std::int32_t( _offset ) * m_step;

			// If m_value is equal to iMax m_value cannot be incremented
			if( steppedOffset > 0 ) assert( offset_t( m_value ) != iMax );

			// If m_value is equal to iMin m_value cannot be decremented
			if( steppedOffset < 0 ) assert( offset_t( m_value ) != iMin );

			// If m_value + _offset would be greater than iMax, m_value cannot be incremented
			if( m_value > 0 && steppedOffset > 0 )
				assert( iMax - offset_t( m_value ) >= steppedOffset );

			// If m_value + _offset would be less than iMin, m_value cannot be decremented
			if( m_value < 0 && steppedOffset < 0 )
				assert( iMin + offset_t( m_value ) < steppedOffset );
		}

		constexpr auto& operator++()noexcept {
			_validate( 1 );
			m_value += m_step;
			return *this;
		}
		constexpr auto operator++( int )noexcept {
			auto cur = *this;
			++( *this );
			return cur;
		}
		constexpr auto& operator--()noexcept {
			_validate( -1 );
			m_value -= m_step;
			return *this;
		}
		constexpr auto operator--( int )noexcept {
			auto cur = *this;
			--( *this );
			return cur;
		}
		constexpr auto operator+( offset_t _offset )const noexcept {
			_validate( _offset );
			return iterator{ m_value + std::int32_t( _offset ) * m_step, m_step };
		}
		constexpr auto operator-( offset_t _offset )const noexcept {
			_validate( _offset );
			return iterator{ m_value - std::int32_t( _offset ) * m_step, m_step };
		}
		constexpr auto& operator+=( offset_t _offset )noexcept {
			*this = *this + _offset;
			return *this;
		}
		constexpr auto& operator-=( offset_t _offset )noexcept {
			*this = *this - _offset;
			return *this;
		}

		constexpr auto operator*()const noexcept {
			return m_value;
		}

		constexpr auto operator==( iterator const& _other )const noexcept {
			return m_value == _other.m_value;
		}
		constexpr auto operator!=( iterator const& _other )const noexcept {
			// Since m_value + m_step may step over the end value so we 
			// cannot use inequality, this way if m_value is greater or equal
			// this returns false
			auto const testA = std::abs( offset_t( m_value ) );
			auto const testB = std::abs( offset_t( _other.m_value ) );
			return testA < testB;
		}

	private:
		std::int32_t m_value = 0, m_step = 1;
	};

public:
	IndexRange() = default;
	constexpr IndexRange( std::int32_t _beg, std::int32_t _end, std::int32_t _step = 1 )noexcept
		:
		m_beg( _beg ), m_end( _end ), m_step( _step )
	{}
	constexpr iterator begin()const noexcept { return iterator{ m_beg, m_step }; }
	constexpr iterator end()const noexcept { return iterator{ m_end, m_step }; }

private:
	std::int32_t m_beg = 0, m_end = 0, m_step;
};
Usage:

Code: Select all

int main( int argc, char* argv[] )
{
	// Loop from 0 to 600 ( 0-599 inclusive ) stepping is 1
	for( const int y : IndexRange( 0, 600 ) )
	{
		// Loop from 0 to 800 ( 0-796 inclusive ) stepping is 4
		for( const int x : IndexRange( 0, 800, 4 ) )
		{

		}
	}

	return 1;
}
I know someone has already designed something like this on here ( MrGodin IIRC ) and I know chili has mentioned the Ranges TS should be coming out for C++20 to help with ranged-for loops, but I thought I'd share nonetheless.

It's works the same as the code below

Code: Select all

	for( int y = 0; y < 600; ++y )
	{
		for( int x = 0; x < 800; x += 4 )
		{
		}
	}
While I made an effort to account for a few possible cases of overflow or underflow, I don't know if I accounted for all possible cases in the IndexRange::iterator::_validate() function. Overflow and underflow on signed integer values is undefined behavior in C++ even if the hardware for x86/x64 machines just wrap the values from positive to negative or negative to positive, which is why I have the _validate() function at all.

There are probably other "gotchas" in there depending on the values you pass in.

One of the things I like about this is being able to declare the "index" you get back from this as const, which is something you can't do with normal for loops. Also, it keeps the value in a limited scope thus allowing you to reuse the same variable name for another loop later on in a function if needed.

You could use it like so as well:

Code: Select all

	std::vector<Entity> entities{ 50 };
	for( auto const i : IndexRange( 0, int( entities.size() ) )
	{
		auto& iEntity = entities[ i ];

		for( auto const j = IndexRange( i, int( entities.size() );
		{
			auto& jEntity = entities[ j ];
			if( iEntity.getBoundingBox().overlaps( jEntity.getBoundingBox() ) )
			{
				iEntity.takeDamage( jEntity.damageValue() );
				jEntity.takeDamage( iEntity.damageValue() );
			}
		}
	}
Naive collision handling I know, but not the point.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Range based for loop using an index

Post by albinopapa » November 2nd, 2019, 10:44 am

I've been thinking it over and I find it difficult to believe that aside from games, there isn't a general need for algorithms that require more than one collection at a time.

I had posted a while back a set of algorithms and iterators that iterate over 2D collections like images or grids for instance. I kind of abandoned the project after using it on a game idea because it was more difficult using those "tools" than just writing raw for loops.

I outlined some issues with such tools in the thread I made then, but I'll go over them here as a refresher.

One pillar of C++ is "zero overhead abstraction". Iterators are an abstraction for pointers or more precisely, a position into some collection. A normal iterator would just be a pointer pointing some element in the collection and for random access containers such as std::array or std::vector you just increment the pointer until the address the pointer points to matches some end pointer. With my 2D iterator implementation, you need to keep track of a pair of values. On top of that, you need a way of telling the iterator to reset the column index and increment the row index, in other words a width or stride. So now you're up to four integer values being carried along with each iterator instantiation. Normally, an iterator wouldn't have access to the container they came from, but it is possible to store a pointer to the parent container to get the dimensions of a 2D container. But let's say you only want to iterate over a region of the container? Now you need to store more information like the boundaries of the region and not the boundaries of the container.

In a 1D container/algorithm/iterator system, if you want the 3rd through the 30th, you just pass iterators at those locations to some algorithm and again, the only information for the 1D iterator is the pointer to which element it represents. For what I wanted to do, I had to create adapter iterators to specify a sub-region.

Say you have a 256x256 sprite sheet and you want to iterate over a 32x32 pixel region at ( 128, 128 ). The iterator needs to be able to start at column 128 and row 128 and once it gets to column 160, it needs to set the X value to 128 + 256 ( the stride or width of the original sprite sheet ). Basically, it needs to have some starting position, know when to move to the next row and still have the correct X position cause it can't be 0. Really what I did is store the starting ( X, Y ) coordinates as an offset and just iterated over the width and height starting from ( 0, 0 ) and adding the offset when dereferrencing.

While all this worked as intended and I didn't notice much change in performance switching back to raw nested loops, I did find it easier, quicker and more flexible to just use the damned loops.

In the example above, aside from being a slow collision handling algorithm, I can't personally thing of a way of using the STL algorithms to be more expressive as it were. I'm not sorting or partitioning, I'm not transforming one collection to another, I'm not copying and I'm not removing.

The closest thing I can think of is std::for_each and at that point, I figure I might as well be using nested for loops.

This is all I could come up with.

Code: Select all

	std::vector<Entity> entities{ 50 };

	int count = 0;
	std::for_each( entities.begin(), entities.end(), [ & ]( Entity& iEntity )
	{
		++count;
		auto jStartIter = entities.begin() + count;
		std::for_each( jStartIter, entities.end(), [ & ]( Entity& jEntity )
		{
			if( iEntity.getBoundingBox().overlaps( jEntity.getBoundingBox() ) )
			{
				iEntity.takeDamage( jEntity.damageValue() );
				jEntity.takeDamage( iEntity.damageValue() );
			}
		} );
	} );
In this particular case, I still needed a "count" variable so I could offset the inner loop's starting iterator each iteration of the outer loop and it's not any clearer what the intention is without having to work through the entire code than with raw for loops ( worse actually if you ask me ).
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
chili
Site Admin
Posts: 3948
Joined: December 31st, 2011, 4:53 pm
Location: Japan
Contact:

Re: Range based for loop using an index

Post by chili » November 2nd, 2019, 11:46 am

albinopapa wrote:
November 2nd, 2019, 10:44 am
I've been thinking it over and I find it difficult to believe that aside from games, there isn't a general need for algorithms that require more than one collection at a time.
Nah, algorithms that need to iterator over two or more containers in parallel are a thing. Python zip does that neatly, as with zip from Ranges.

The thing you made up there seems useful, but what I often find myself wanting is a way to range based for over a container, but also get the index for some reason. In these cases I just do a naked for loop. It would be neat to have a template class that can return an iterator which dereferences to a pair of index,T, but I guess it would be annoying because sometime you want T, and other times you want T& or const T&. suppose you could just make 2 classes tho

Code: Select all

for( auto element : ConstRefIndexed{ container } )
{
	element.i; // the index
	element.cref; // const ref to the value
}
Chili

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Range based for loop using an index

Post by albinopapa » November 2nd, 2019, 8:12 pm

You know, my original motivation was exactly what you pointed out ( index, ref/cref ) and somehow turned into just ( index ). I suppose there are C++ iterators ( std::back_insert_iterator ) that do interact with the container, though I would more consider these iterator adapters or facades.

So, challenge accepted.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Range based for loop using an index

Post by albinopapa » November 3rd, 2019, 8:45 am

Code: Select all

#include <iterator>
#include <type_traits>
#include <utility>
std::pair<size_t, T*> iterator version

Code: Select all

template<typename ContainerValueType>
class IndexRefIterator
{
public:
	using iterator_category = std::random_access_iterator_tag;
	using value_type = std::pair<std::size_t, ContainerValueType*>;
	using reference = std::pair<std::size_t, ContainerValueType*>&;
	using difference_type = std::ptrdiff_t;
public:
	IndexRefIterator() = default;
	constexpr IndexRefIterator( std::size_t _index, ContainerValueType* _pValue )noexcept
		:
		m_pair( _index, _pValue )
	{}

	constexpr auto& operator++()noexcept {
		++m_pair.first;
		++m_pair.second;
		return *this;
	}
	constexpr auto operator++( int )noexcept {
		auto cur = *this;
		++( *this );
		return cur;
	}
	constexpr auto& operator--()noexcept {
		--m_pair.first;
		--m_pair.second;
		return *this;
	}
	constexpr auto operator--( int )noexcept {
		auto cur = *this;
		--( *this );
		return cur;
	}
	constexpr auto operator+( difference_type _offset )const noexcept {
		return IndexRefIterator{ 
			std::size_t( difference_type( m_pair.first ) + _offset ),
			m_pair.second + _offset 
		};
	}
	constexpr auto operator-( difference_type _offset )const noexcept {
		return IndexRefIterator{
			std::size_t( difference_type( m_pair.first ) - _offset ),
			m_pair.second - _offset
		};
	}
	constexpr auto& operator+=( difference_type _offset )noexcept {
		*this = *this + _offset;
		return *this;
	}
	constexpr auto& operator-=( difference_type _offset )noexcept {
		*this = *this - _offset;
		return *this;
	}
	constexpr auto operator-( IndexRefIterator const _other )const noexcept {
		return difference_type( m_pair.first - _other.m_pair.first );
	}
	constexpr reference operator*()noexcept {
		return m_pair;
	}

	constexpr auto operator==( IndexRefIterator const& _other )const noexcept {
		return m_pair.first == _other.m_pair.first && m_pair.second == _other.m_pair.second;
	}
	constexpr auto operator!=( IndexRefIterator const& _other )const noexcept {
		return !( *this == _other );
	}

private:
	value_type m_pair;
};
std::pair<size_t, T const*> iterator version

Code: Select all

template<typename ContainerValueType>
class IndexCRefIterator
{
public:
	using iterator_category = std::random_access_iterator_tag;
	using value_type = std::pair<std::size_t, ContainerValueType const*>;
	using reference = std::pair<std::size_t, ContainerValueType const*>const&;
	using difference_type = std::ptrdiff_t;
	
public:
	IndexCRefIterator() = default;
	constexpr IndexCRefIterator( std::size_t _index, ContainerValueType const* _pValue )noexcept
		:
		m_pair( _index, _pValue )
	{}

	constexpr auto& operator++()noexcept {
		++m_pair.first;
		++m_pair.second;
		return *this;
	}
	constexpr auto operator++( int )noexcept {
		auto cur = *this;
		++( *this );
		return cur;
	}
	constexpr auto& operator--()noexcept {
		--m_pair.first;
		--m_pair.second;
		return *this;
	}
	constexpr auto operator--( int )noexcept {
		auto cur = *this;
		--( *this );
		return cur;
	}
	constexpr auto operator+( difference_type _offset )const noexcept {
		return IndexCRefIterator{ 
			std::size_t( std::ptrdiff_t( m_pair.first ) + _offset ),
			m_pair.second + _offset 
		};
	}
	constexpr auto operator-( difference_type _offset )const noexcept {
		return IndexCRefIterator{
			std::size_t( difference_type( m_pair.first ) - _offset ),
			m_pair.second - _offset
		};
	}
	constexpr auto& operator+=( difference_type _offset )noexcept {
		*this = *this + _offset;
		return *this;
	}
	constexpr auto& operator-=( difference_type _offset )noexcept {
		*this = *this - _offset;
		return *this;
	}
	constexpr auto operator-( IndexCRefIterator const _other )const noexcept {
		return difference_type( m_pair.first - _other.m_pair.first );
	}
	constexpr reference operator*()const noexcept {
		return m_pair;
	}

	constexpr auto operator==( IndexCRefIterator const& _other )const noexcept {
		return m_pair.first == _other.m_pair.first && m_pair.second == _other.m_pair.second;
	}
	constexpr auto operator!=( IndexCRefIterator const& _other )const noexcept {
		return !( *this == _other );
	}


private:
	value_type m_pair;
};
Adapter for random access containers only

Code: Select all

// Empty base class
template<typename T> class IndexIteratorAdapter {};

Code: Select all

// Non-const reference adapter for random access container
template<typename T>
class IndexIteratorAdapter<T&>
{
public:
	using iterator =  IndexRefIterator<typename T::value_type>;

public:
	IndexIteratorAdapter() = default;
	IndexIteratorAdapter( T& _container )noexcept
		:
		m_container( _container )
	{}

	iterator begin()noexcept {
		return iterator( 0, m_container.data() );
	}
	iterator end()noexcept {
		return iterator( m_container.size(), m_container.data() + m_container.size() );
	}

private:
	T& m_container;
};

Code: Select all

// Const reference adapter for random access container
template<typename T>
class IndexIteratorAdapter<const T&>
{
public:
	using iterator = IndexCRefIterator<typename std::remove_reference_t<T>::value_type>;

public:
	IndexIteratorAdapter() = default;
	IndexIteratorAdapter( T const& _container )noexcept
		:
		m_container( _container )
	{}
	iterator begin()const noexcept {
		return iterator( 0, m_container.data() );
	}
	iterator end()const noexcept {
		return iterator( m_container.size(), m_container.data() + m_container.size() );
	}
private:
	T const& m_container;
};

Code: Select all

// Class template argument deduction guides
template<typename T> IndexIteratorAdapter( T const& )->IndexIteratorAdapter<T const&>;
template<typename T> IndexIteratorAdapter( T& )->IndexIteratorAdapter<T&>;
Usage:

Code: Select all

#include <iostream>
#include <vector>

struct Thing
{ 
	int m_value;
};
int main( int argc, char* argv[] )
{
	auto things = std::vector<Thing>{ 25 };
	auto const_test = []( std::vector<Thing> const& _things )
	{
		for( auto[index, thing]: IndexIteratorAdapter( _things ) )
		{
			std::cout << "Value: "<< thing->m_value << ", Index: " << index << '\n';
		}
	};
	auto nonConst_test = []( std::vector<Thing>& _things )
	{
		for( auto[ index, thing ] : IndexIteratorAdapter( _things ) )
		{
			thing->m_value = index * 200 + 100;
		}
	};

	nonConst_test( things );
	const_test( things );

	return 1;
}
// Results:
// Value: 0, Index: 0
// Value: 200, Index: 1
// Value: 400, Index: 2
// Value: 600, Index: 3
// Value: 800, Index: 4
I tried several different ways of using a single "adapter" class, but to no avail as even though I was passing a const&, the compiler kept wanting to use the non-const begin()/end() functions. Even using std::enable_if_t wouldn't avoid this frustrating situation. Luckily, template specialization and class template argument deduction saves the day and I get the same interface for const and non-const versions.

I thought about templating the index type, but since you aren't passing in an int to the constructor of the class, it can't be deduced and would require template parameters. May not be a big deal, but to me it seams odd to have one type deduced and have to explicitly declare the other.

auto adapter = IndexIteratorAdapter<int>( _things ) ;

It isn't clear what the <int> does, is it a cast? Is it describing what type the container value is? Hopefully, you get the point. Also, since I am using the size of the container as the ending point, that's what I compare for the end condition on top of the pointer value.

I tried doing std::pair<size_t, T&> and std::pair<size_t, T const&> in my first attempt, by just returning a freshly constructed pair, by storing a pointer and dereferencing it, but when using structured bindings the value was lost. Since references can't be reassigned, I couldn't just store an std::pair<size_t, T&> as well as I'd have no way of incrementing the reference, so std::pair<size_t, T (const) *> it is.

On a side note, I just realized after hours of banging my head why the compiler wanted to use the non-const versions of begin() and end(). The IndexIteratorAdapter was not const since I am creating it just for the loop. It probably would have worked if I had instantiated a const IndexIteratorAdapter variable outside the loop.

auto const adapter = IndexIteratorAdapter( things );
This would have forced the compiler to use the const begin()/end() functions...sigh, the things you realize after the fact. I like how it turned out though, having to create the adapter outside the loop would make it less enticing to use.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Range based for loop using an index

Post by albinopapa » November 3rd, 2019, 9:00 am

The deduction guides seem to be necessary, though I am not sure why. If I remove the deduction guides it says that the class has no suitable constructor ( I'm guessing the compiler is trying to use the empty base class instead of the specialized classes ).
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Range based for loop using an index

Post by albinopapa » November 3rd, 2019, 9:52 am

You know, I'm not sure why I didn't just create my own struct instead of using pair. Would have accomplished the same thing as using std::pair with structured bindings. Creating your own struct that works with structured bindings seems like a long process to go through, but I haven't tried it yet.

Also, you might notice I didn't jump through the mathematical hoops and unsigned range limitations of validating the index value on this one.
This would only be necessary if you actually create one of these iterators outside of using the range for loop or STL algorithms and want to offset the iterator using the +/- operators. I probably shouldn't have included those operator overloads to avoid this vary situation actually.

I could probably make this a little more flexible to work with other container types if instead of a raw pointer I stored/used the container's own interator. The operator overloads, excluding ++/-- and the distance/difference operator- overload, would have to go. Unfortunately, std::forward_list doesn't have a decrement operator, and std::prev() required a bidirectional iterator, so I'd have to make different iterators and adapter specializations for some of the containers in the STL. Most of the time, std::vector or std::array, which are both random access containers, are probably going to be used so I'm good with what is provided.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Range based for loop using an index

Post by albinopapa » November 4th, 2019, 5:30 pm

Just tried my theory out on godbolt.org and it seems I was right.

Code: Select all

	auto const adapter = IndexIteratorAdapter( _things );
	for( auto[ index, thing ] : adapter )
	{
		//thing.m_value = 42;  Fails to compile as it should
	         auto v = thing.m_value;
	}
If the adapter's const-ness matches that of it's container, then a single class can be used. I just don't think there is a "pretty" way of keeping them in sync...Well, maybe passing the adapter to the function instead of the container, but that pretty much changes the interface of things and that's a big ole refactor for ya.

With extended lifetime of const references, I suppose you could also const_cast the adapter constructor:

Code: Select all

		for( auto[ index, thing ] : const_cast<IndexIteratorAdapter<decltype(_things)> const&&>(IndexIteratorAdapter(_things) ) )
This works according to compiler explorer, but looks terrible.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
chili
Site Admin
Posts: 3948
Joined: December 31st, 2011, 4:53 pm
Location: Japan
Contact:

Re: Range based for loop using an index

Post by chili » November 5th, 2019, 2:21 pm

Looks like you were busy! I want to believe there is a way to get the destructuring working with an iterator that yields index and (const) reference to value.
Chili

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Range based for loop using an index

Post by albinopapa » November 5th, 2019, 2:49 pm

Well, I'll be damned.

Code: Select all

template<typename ContainerValueType>
class IndexRefIterator
{
public:
	using iterator_category = std::random_access_iterator_tag;
	using value_type = std::pair<std::size_t, ContainerValueType*>;
	using reference = std::pair<std::size_t, ContainerValueType&>;
	using difference_type = std::ptrdiff_t;
public:
	IndexRefIterator() = default;
	constexpr IndexRefIterator( std::size_t _index, ContainerValueType* _pValue )noexcept
		:
		m_pair( _index, _pValue )
	{}

	constexpr auto& operator++()noexcept {
		++m_pair.first;
		++m_pair.second;
		return *this;
	}
	constexpr auto operator++( int )noexcept {
		auto cur = *this;
		++( *this );
		return cur;
	}
	constexpr auto& operator--()noexcept {
		--m_pair.first;
		--m_pair.second;
		return *this;
	}
	constexpr auto operator--( int )noexcept {
		auto cur = *this;
		--( *this );
		return cur;
	}
	constexpr auto operator+( difference_type _offset )const noexcept {
		return IndexRefIterator{ 
			std::size_t( difference_type( m_pair.first ) + _offset ),
			m_pair.second + _offset 
		};
	}
	constexpr auto operator-( difference_type _offset )const noexcept {
		return IndexRefIterator{
			std::size_t( difference_type( m_pair.first ) - _offset ),
			m_pair.second - _offset
		};
	}
	constexpr auto& operator+=( difference_type _offset )noexcept {
		*this = *this + _offset;
		return *this;
	}
	constexpr auto& operator-=( difference_type _offset )noexcept {
		*this = *this - _offset;
		return *this;
	}
	constexpr auto operator-( IndexRefIterator const _other )const noexcept {
		return difference_type( m_pair.first - _other.m_pair.first );
	}
	constexpr reference operator*()noexcept {
		return reference( m_pair.first, *m_pair.second );
	}

	constexpr auto operator==( IndexRefIterator const& _other )const noexcept {
		return m_pair.first == _other.m_pair.first && m_pair.second == _other.m_pair.second;
	}
	constexpr auto operator!=( IndexRefIterator const& _other )const noexcept {
		return !( *this == _other );
	}

private:
	value_type m_pair;
};

Code: Select all

template<typename ContainerValueType>
class IndexCRefIterator
{
public:
	using iterator_category = std::random_access_iterator_tag;
	using value_type = std::pair<std::size_t, ContainerValueType const*>;
	using reference = std::pair<std::size_t, ContainerValueType const&>;
	using difference_type = std::ptrdiff_t;
	
public:
	IndexCRefIterator() = default;
	constexpr IndexCRefIterator( std::size_t _index, ContainerValueType const* _pValue )noexcept
		:
		m_pair( _index, _pValue )
	{}

	constexpr auto& operator++()noexcept {
		++m_pair.first;
		++m_pair.second;
		return *this;
	}
	constexpr auto operator++( int )noexcept {
		auto cur = *this;
		++( *this );
		return cur;
	}
	constexpr auto& operator--()noexcept {
		--m_pair.first;
		--m_pair.second;
		return *this;
	}
	constexpr auto operator--( int )noexcept {
		auto cur = *this;
		--( *this );
		return cur;
	}
	constexpr auto operator+( difference_type _offset )const noexcept {
		return IndexCRefIterator{ 
			std::size_t( std::ptrdiff_t( m_pair.first ) + _offset ),
			m_pair.second + _offset 
		};
	}
	constexpr auto operator-( difference_type _offset )const noexcept {
		return IndexCRefIterator{
			std::size_t( difference_type( m_pair.first ) - _offset ),
			m_pair.second - _offset
		};
	}
	constexpr auto& operator+=( difference_type _offset )noexcept {
		*this = *this + _offset;
		return *this;
	}
	constexpr auto& operator-=( difference_type _offset )noexcept {
		*this = *this - _offset;
		return *this;
	}
	constexpr auto operator-( IndexCRefIterator const _other )const noexcept {
		return difference_type( m_pair.first - _other.m_pair.first );
	}
	constexpr reference operator*()const noexcept {
		return reference( m_pair.first, *m_pair.second );
	}

	constexpr auto operator==( IndexCRefIterator const& _other )const noexcept {
		return m_pair.first == _other.m_pair.first && m_pair.second == _other.m_pair.second;
	}
	constexpr auto operator!=( IndexCRefIterator const& _other )const noexcept {
		return !( *this == _other );
	}


private:
	value_type m_pair;
};
Don't know what the difference is between this and what I had before that didn't work, but here you go, it does work.

Literally the only difference is instead of storing the index and pointer separately, I'm storing them in a pair in class. I'm still dereferencing the pointer to make the index, ref/cref pair and returning by value, so there shouldn't be a difference.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Post Reply