C++ TArray and ConstIterators

Here’s a snippet from Array.h:

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/** STL-like iterators to enable range-based for loop support. */
#if PLATFORM_COMPILER_HAS_RANGED_FOR_LOOP

	FORCEINLINE TIterator begin()
	{
		return TIterator(*this);
	}

	FORCEINLINE TConstIterator begin() const
	{
		return TConstIterator(*this);
	}

	FORCEINLINE TIterator end()
	{
		return TIterator(*this, Num());
	}

	FORCEINLINE TConstIterator end() const
	{
		return TConstIterator(*this, Num());
	}

#endif

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

How do you use TConstIterators?

Yes, you can. The difference is the const. For example, here is an ancient example:

class FScriptArray : protected FHeapAllocator::ForAnyElementType
{
public:
	void* GetData()
	{
		return this->GetAllocation();
	}
	const void* GetData() const
	{
		return this->GetAllocation();
	}

This is used all over the place.

No. const void* GetData() const now has a different signature (the final “const”) and means that it can be called from other functions that are also const.

(And yes, I realize if I made my function a const function - or made the object const - then it would call the correct iterator. But that doesn’t mean this isn’t a bug!)

I am missing something here. The two examples are completely analougous to my read.

Okay, let’s say that I have a function like this:

void OCPlayerController::SelectActor( AActor * p_actor )
{
		for(TArray< class UActorComponent* >::TConstIterator it = p_actor->Components.begin(); it != p_actor->Components.end(); ++it)
{
    // Do something here
}

…then you get:

1> error C2440: 'initializing' : cannot convert from 'TIndexedContainerIterator' to 'TIndexedContainerIterator'
1>          with
1>          [
1>              ContainerType=TArray,
1>              ElementType=UActorComponent *,
1>              IndexType=int32
1>          ]
1>          and
1>          [
1>              ContainerType=const TArray,
1>              ElementType=UActorComponent *const ,
1>              IndexType=int32
1>          ]
1>          No constructor could take the source type, or constructor overload resolution was ambiguous

But if I change the code to be:

void OCPlayerController::SelectActor( AActor * p_actor )
{
		for(TArray< class UActorComponent* >::TIterator it = p_actor->Components.begin(); it != p_actor->Components.end(); ++it)
{
    // Do something here
}

…then it compiles and works. Obviously this is because p_actor->Components is non-const. But I will always choose to use const over non-const except where I’m changing a value. Not allowing me to do so is dangerous! :wink:

Anyway, the point I’m making is that I should be able to use const iterators from non-const functions. I shouldn’t only be able to use const iterators on const objects.

begin and end are for “ranged for loops”. Use CreateIterator and CreateConstIterator instead. I will add steve to the ticket. He can talk about why the ranged for is set up that way.

Here is an example from the same file:

		for( auto It=Items.CreateConstIterator(); It; ++It )

Ha! I tried to use CreateConstIterator() originally but it wasn’t obvious how I was supposed to check that the iterator was still in bounds - which is why I ended up in the begin()…end() rabbit hole. :slight_smile:

Which file is that example from?

Hi Neil,

Glad the confusion regarding the non-const/const member function has been sorted out. The code certainly isn’t illegal, though it may be missing functionality… see below.

begin() and end() were added simply to support C++11’s ranged-for syntax, that is:

for (UActorComponent* Comp : p_actor->Components)
{
    // Use Comp here
}

Those functions are directly called by the language when this syntactic form is used. They weren’t really intended to be used directly by developers, which has lead to the confusion.

The other part of the confusion is caused by the fact that our TIterators are not implicitly (nor explicitly, for that matter) convertible to their TConstIterator equivalent. This is what allows you to call .begin() on a non-const STL container and have it assigned to a const_iterator.

We could add support for that but, as I say, begin/end weren’t really intended to be used directly. CreateIterator and CreateConstIterator are the intention here, as Gil has already mentioned. We could add those conversions to our iterators, but it would be for a workflow which we don’t plan to support.

Hope this clears up the issue,

Steve

PS. Template arguments are also make up a function’s signature, which means you can legally have functions defined as follows, which otherwise look like they only vary by return type:

template <typename T>
T GetT();

Nope, it certainly wasn’t illegal. I missed the const keyword which would, of course, change the signature. Gil was right - and that’s why I changed the topic so that I wouldn’t be misleading - and to lead people down the right path in future!

The problem currently is that a large majority of the code is undocumented. It’s quite readable, kudos to Epic for that, but finding out how to get all of the sockets for a given actor sent me down a winding rabbit hole and ultimately led me to the TArray class.

I’m perfectly fine using TConstIterator and I appreciate the help from both Gil and yourself - I just hope that in future the headers can be better documented so I can see what exactly I’m meant to use in order to iterate over things such as a TArray. :slight_smile:

It’s a fair point, though it’s likely that the sort of documentation you’re after would be ‘offline’, i.e. not in the headers.

The ranged-for syntax support is relatively new, as it wasn’t until recently (when we supported VC2012) that we had parity for this feature across our supported compilers. The problem with the STL-like naming is that people like yourself naturally assume that it’s STL-like in its semantics, which it isn’t, and it fails when you try to use it as such. But the readability and maintainability advantages of ranged-for syntax is too great to ignore.

However, I will add some comments to those begin/end functions which say they’re not intended to be used directly.

Steve

I see what Neil means. There’s a big difference between, say, TArray and std::vector:

TArray int_array;
TArray::TIterator iter = int_array.begin();
TArray::TConstIterator const_iter = int_array.begin();

std::vector int_vec;
std::vector::iterator vec_iter = int_vec.begin();
std::vector::const_iterator vec_iter_const = int_vec.begin();

With std::vector, you can easily get the const_iterator from the non_const vector object, but with TArray, you get a compile error on the third line.

… And you can certainly get around it by declaring another const& TArray and setting it equal to int_array and calling that on it, but you shouldn’t have to.

I will say that the topic was definitely misnamed because I hadn’t noticed the const modifier on the ConstInterator functions when I first posted it. I’ve just renamed it to be more useful for anybody searching in future.

Sorry!