Download

Proper way to declare/define member TStaticArrays?

Hello everybody! Thanks in advance for putting up with my newborn question…

I am working on a Tychoon style terrain generation system. Obviously it uses a lot of dynamic and static arrays often in multidimensional forms and they’re all over the place. The problem I am having, however, is properly defining them and instantiating them in my class constructor.

In one of my classes, TerrainTile, I have the following declarations in the header:



class MGTYCHOON_API TerrainTile
{
public:
    TerrainTile(TStaticArray4<uint32> set_vertices = { 0, 0, 0, 0 });
    // ...
private:
    bool isRendered;
    TStaticArray4<uint32> vertices;
    static TStaticArray4< TStaticArray2<uint32>> vertex_xy_coords;
    // ...
}


The implementation in TerrainTile.cpp looks like this:



// ...

TerrainTile::TerrainTile(TStaticArray4<uint32> set_vertices)
{
    isRendered = true;
    vertices = set_vertices;
}

// ...

TStaticArray4< TStaticArray2<uint32>> TerrainTile::vertex_xy_coords = {
    { 0, 0 },    // v0
    { 1, 0 },    // v1
    { 1, 1 },    // v2
    { 0, 1 }     // v3
};

// ...


The idea is to crate a TerrainTile object with default ‘vertices’ values of [0, 0, 0, 0] (those are elevation values for each vertex). When a class is instantiated, the idea is that it will have a default elevation of “0” for each vertex while the static array ‘vertex_xy_coords’ contains a reference of (x,y) coordinates (relative to the tile origin).

Obviously I am doing this wrong, because the compiler complains that I don’t have appropriate constructors for my TStaticArray objects:



Error	2	error C2512: 'TStaticArray4<uint32>' : no appropriate default constructor available	C:\...]\Unreal Projects\MGTychoon\Source\MGTychoon\TerrainTile.cpp	7	1	MGTychoon


In another file, “Terrain.cpp” I have another class that uses this kind of nested TStaticArray but spits a much more confusing error. Here’s the code:



TStaticArray2< TStaticArray3<uint32>> ATerrain::DefineSurfaceTriangles(bool isRotated)
{
    if (isRotated)
    {
        return {
            { 1, 2, 3 },
            { 3, 0, 1 }
        };
    }
    else
    {
        return {
            { 0, 1, 2 },
            { 2, 3, 1 }
        };
    }
}


And the error:



Error	5	error C2512: 'TStaticArray3<uint32>' : no appropriate default constructor available	C:\...]\Epic Games\4.4\Engine\Source\Runtime\Core\Public\Containers\StaticArray.h	20	1	MGTychoon


This is more frustrating because the error complains about the library ‘StaticArray.h’ (which I have to assume works fine). I had to sift through the compiler output to find the bad code (seen above).

I am obviously new to C++, so I’m a little out of my element. I can’t pick out what I’m doing wrong, and I can’t find enough information on TStaticArrays to figure out how to do this correctly. Before I started working with VisualStudio and the Unreal Engine I wrote a different version of these two classes where I did stuff like this frequently with ‘std::vector’.

Can anybody tell me the correct way to declare a TStaticArray class member and define its initial values in my class constructor?

Again, any help will be greatly appreciated (I also have an Unreal Engine answer hub question open where I will post any answers).

Here’s the answer…

I figured out what was going wrong. I have been implementing TStaticArray objects incorrectly.

First of all, the “shortcut” types (eg: TStaticArray2) cannot be defined as multidimensional, while the base class TStaticArray can…



// This will fail:
TStaticArray2< TStaticArray2<uint32>> md_array_bad;
// This is okay:
TStaticArray< TStaticArray<uint32, 2>, 2> md_array_good;


Though in both cases, TStaticArray objects cannot be defined using the {,} syle…



// All of these are bad:
TStaticArray2<uint32> some_vals_0 = { 0, 1 };
TStaticArray<uint32, 2> some_vals_1 = { 2, 3 };
TStaticArray< TStaticArray<uint32, 2>,  2> md_vals_0 = {
    { 0, 1 },
    { 2, 3 }
}


I have only found two ways to properly define any TStaticArray object…



// Using some kind of loop with reference values...
 TArray<uint32> some_vals = { 2, 3, 1, 6 };
 TStaticArray< TStaticArray<uint32, 2>, 2> md_array;
 for (uint32 x = 0; x < 2; x++)
 {
     for (uint32 y = 0; y < 2; y++)
     {
         for (uint32 i = 0; i < 4; i++)
         {
             md_array[x][y] = some_vals*;
         }
     }
 }
 
 // Defining each value manually...
 TStaticArray< TStaticArray<uint32, 2>, 2> md_array;
 md_array[0][0] = 2;
 md_array[0][1] = 3;
 md_array[1][0] = 1;
 md_array[1][1] = 6;


PS: I posted a more thorough explanation on Answer Hub.

PSS: If I am simply doing this wrong and there is some way to define a TStaticArray object using the {,} technique… I pray: do tell.

As it stands right now you can’t. To initialize TStaticArray using an initializer list like you’ve attempted to do would require the implementation of an additional constructor that takes a std::initializer_list argument. The use of the STL is generally avoided in UE4 so I don’t know if this is a change Epic would be willing to make.

Thank you so much for your reply! That finally puts this to bed for me. Now I know I’m not just doing something very wrong.

I do hope they include list initialization for TStaticArrays so they share more syntax with TArray (which does take them). Maybe one day I’ll learn enough to add it myself :eek:

PS: Apparently the shortcuts can handle it in some form. “TStaticArray2<uint32> a_list = { 0, 1 };” totally does work, as long as it’s provided in the class declaration (though the long form “TStaticArray<uint32, 2> a_list = { 0, 1 };” fails).

Actually, this still raises an interesting question (with which I am still having a difficulty)…

What is the best practice to define a TArray or TStaticArray object (particularly when they are static class members)?

This works with TArray? I don’t see how.

Yes, TStaticArray2/3/4 have constructors that take a fixed number of elements and the compiler is able to match a brace enclosed list of elements with the constructor parameters.

Correction: I thought it worked. IntelliSense hadn’t caught up with my tom foolery.

Thanks again for clarifying.

You’d declare the static member TArray/TStaticArray, and then initialize, and populate it separately.



// MyClass.h
class MyClass
{
    static TArray<FString> MyStrings;

    static void PopulateMyStrings();
}

// MyClass.cpp
TArray<FString> MyClass::MyStrings;

void MyClass::PopulateMyStrings()
{
    MyStrings.Add(TEXT("One"));
    MyStrings.Add(TEXT("Two"));
    MyStrings.Add(TEXT("Three"));
}


With TStaticArray2/3/4 you’d do this instead:



// MyClass.h
class MyClass
{
    static TStaticArray2<FString> My2Strings;
    static TStaticArray3<FString> My3Strings;
    static TStaticArray4<FString> My4Strings;
}

// MyClass.cpp
TStaticArray2<FString> MyClass::My2Strings { TEXT("One"), TEXT("Two") };
TStaticArray3<FString> MyClass::My3Strings { TEXT("One"), TEXT("Two"), TEXT("Three") };
TStaticArray4<FString> MyClass::My4Strings { TEXT("One"), TEXT("Two"), TEXT("Three"), TEXT("Four") };