Using Find on TMap with custom KeyFuncs

Greetings to community and Epic’s staff! I would appreciate your help.

I use Unreal Engine 4.21 master branch on Windows 7 64 bit Maximal.
I tried to declare own KeyFuncs and used TMap with it for learning purposes.
Construction or using the Add operation on the map compiles well.

The problem is that using any of Find operations (I tried Find, FindRef, FindOrAdd) fails to compile, but only when I use the custom KeyFuncs in the TMap.
I tried to pass both KeyFuncs::KeyInitType or KeyFuncs::ElementInitType arguments to Find operations.

I’m using FMagicItem user-defined struct as a key of TMap; Tried to declare the struct both with USTRUCT() and without it.
By default it’s indended that two FMagicItem instances are to be treated as equal if both their ClassName and PowerLevel are equal.
So I provided the operator==/operator!= and GetTypeHash global functions for the FMagicItem structure, and it compiled well on the TMap<FMagicItem, int32>.
User KeyFuncs are provided to treat two FMagicItem instances equal by only comparing ClassName.

Because I use the ClassName as a Key to Compare, I declared FString as KeyInitType in the KeyFuncs.
Also I declared the Matches ans GetKeyHash static functions in the KeyFuncs.
I tried to declare both functions taking const KeyInitType& and functions taking KeyInitType.

When I constructed the TMap<FMagicItem, int32, FDefaultSetAllocator, MagicItemMapKeyFuncs_ByClassName<int32>> instance the problem arose.

The KeyFuncs is declared as such (I tried to omit the “public” when inheriting from BaseFuncs):



template<class ValueType>
struct MagicItemMapKeyFuncs_ByClassName :
    public BaseKeyFuncs
    <
        TPair<FMagicItem,ValueType>,
        FString    
   >
{
private:
    using Super = BaseKeyFuncs<TPair<FMagicItem,ValueType>>, FString>;

public:
    using KeyInitType = Super::KeyInitType;
    using ElementInitType = Super::ElementInitType;

    static KeyInitType GetSetKey(const ElementInitType& InElement)
    {
        return InElement.Key.ClassName;
    }

    static bool Matches(const KeyInitType& A, const KeyInitType& B)
    {
        return 0 == A.Compare(B, ESearchCase::CaseSensitive);
    }

    static uint32 GetKeyHash(const KeyInitType& K)
    {
        return FCrc::StrCrc32(*K);
    }    
};


The struct itself is:
[SPOILER]



struct FMagicItem
{
    FString ClassName;
    FText Description;
    int32 PowerLevel;

    explicit FMagicItem(const FString& InClassName) :
        ClassName{InClassName}
    ,    Description{FText::FromString("")}
    ,    PowerLevel{ 0 }
    {
    }

    FMagicItem(const FString& InClassName, const FText& InDescription, int32 InPowerLevel) :
        ClassName{InClassName}
    ,    Description{InDescription}
    ,    PowerLevel{InPowerLevel}
    {
    }
};


uint32 GetTypeHash(const FMagicItem& Value)
{
    uint32 const ClassNameHash = FCrc::StrCrc32(*Value.ClassName);
    uint32 PowerLevelHash = GetTypeHash(Value.PowerLevel);
    return ClassNameHash ^ PowerLevelHash;
}

bool operator==(const FMagicItem& A, const FMagicItem& B)
{
    return (0 == A.ClassName.Compare(B.ClassName, ESearchCase::CaseSensitive)) &&
        (A.PowerLevel == B.PowerLevel);
}
bool operator!=(const FMagicItem& A, const FMagicItem& B)
{
    return !(A == B);
}


[/SPOILER]

Of course MagicItemMapKeyFuncs_ByClassName is declared AFTER the struct itself.
Entire code is located inside a single .cpp file (except of Blueprint library header itself).
Full .cpp source code:
[SPOILER]



#include "MapExampleBlueprintLibrary.h"
#include "Logging/LogMacros.h"

#define LOCTEXT_NAMESPACE "MapExampleBlueprintLibrary"

DECLARE_LOG_CATEGORY_EXTERN(MapExLog, Display, All);
DEFINE_LOG_CATEGORY(MapExLog);

struct FMagicItem
{
    FString ClassName;
    FText Description;
    int32 PowerLevel;

    explicit FMagicItem(const FString& InClassName) :
        ClassName{InClassName}
    ,    Description{FText::FromString("")}
    ,    PowerLevel{ 0 }
    {
    }

    FMagicItem(const FString& InClassName, const FText& InDescription, int32 InPowerLevel) :
        ClassName{InClassName}
    ,    Description{InDescription}
    ,    PowerLevel{InPowerLevel}
    {
    }
};


uint32 GetTypeHash(const FMagicItem& Value)
{
    uint32 const ClassNameHash = FCrc::StrCrc32(*Value.ClassName);
    uint32 PowerLevelHash = GetTypeHash(Value.PowerLevel);
    return ClassNameHash ^ PowerLevelHash;
}

bool operator==(const FMagicItem& A, const FMagicItem& B)
{
    return (0 == A.ClassName.Compare(B.ClassName, ESearchCase::CaseSensitive)) &&
        (A.PowerLevel == B.PowerLevel);
}
bool operator!=(const FMagicItem& A, const FMagicItem& B)
{
    return !(A == B);
}

template<class ValueType>
struct MagicItemMapKeyFuncs_ByClassName :
    public BaseKeyFuncs
    <
        TPair<FMagicItem,ValueType>,
        FString    
    >
{
private:
    using Super = BaseKeyFuncs<TPair<FMagicItem,ValueType>, FString>;

public:
    using KeyInitType = Super::KeyInitType;
    using ElementInitType = Super::ElementInitType;

    static KeyInitType GetSetKey(const ElementInitType&  InElement)
    {
        return InElement.Key.ClassName;
    }

    static bool Matches(const KeyInitType& A, const KeyInitType&  B)
    {
        return 0 == A.Compare(B, ESearchCase::CaseSensitive);
    }

    static uint32 GetKeyHash(const KeyInitType& K)
    {
        return FCrc::StrCrc32(*K);
    }    
};

FMagicItem const Sword(FString("Sword"), LOCTEXT("SwordDescription", "Ordinary sword"), 0);
FMagicItem const Sword_OtherDesc(FString("Sword"), LOCTEXT("OtherSwordDescription", "Ordinary sword (Updated description)"), 0);
FMagicItem const PowerSword(FString("Sword"), LOCTEXT("PowerSwordDescrption", "Power sword"), 100);

void UMapExampleBlueprintLibrary::MapCustomKeyFuncsExample()
{
    UE_LOG(MapExLog, Display, TEXT("UMapExampleBlueprintLibrary::MapCustomKeyFuncsExample"));

    // Construction: Compiles well
    TMap<FMagicItem, int32, FDefaultSetAllocator, MagicItemMapKeyFuncs_ByClassName<int32>> ItemToCount;
    // Adding: Compiles well
    int32 InitialSwordCount = ItemToCount.Add(Sword, 1);

    // WARNING!!! Find fails to compile!
   // (I tried both ItemToCount.Find(Sword) and ItemToCount.Find(Sword.ClassName))
    const int32* const pInitialSwordCount_Found = ItemToCount.Find(Sword);

    // WARNING: Fails to compile:
    int32 InitialSwordCount_FoundByRef = ItemToCount.FindRef(Sword);

    // WARNING!!! Fails to compile:
    int32 InitialPowerSwordCount = ItemToCount.FindOrAdd(PowerSword);
}


[/SPOILER]
It’s my error log, but it’s not very helpful due to encoding problems;
[SPOILER]



1>------ Сборка начата: проект: ShaderCompileWorker, Конфигурация: Development_Program x64 ------
1>Using 'git status' to determine working set for adaptive non-unity build.
1>Waiting for 'git status' command to complete
1>Target is up to date
1>Copying C:\UNR_CODE\UnrealEngine\Engine\Binaries\Win64\ShaderCompileWorker.exe to C:\UNR_CODE\UnrealEngine\Engine\Binaries\Win64\XGEControlWorker.exe
1>Скопировано файлов: 1.
1>Deploying ShaderCompileWorker Win64 Development...
1>Total build time: 6,81 seconds (NoActionsToExecute executor: 0,00 seconds)
2>------ Сборка начата: проект: CPP_XPR_COMPILE, Конфигурация: Development_Editor x64 ------
2>Using 'git status' to determine working set for adaptive non-unity build.
2>Building 3 actions with 4 processes...
2> MapExampleBlueprintLibrary.cpp
2>C:\UNR_CODE\UnrealEngine\Engine\Source\Runtime\Core\Public\Containers/Map.h(382): error C2664: "const TTuple<KeyType,ValueType> *TSet<TTuple<KeyType,ValueType>,KeyFuncs,SetAllocator>::Find(const FString &) const": ?????????? ????????? ????? 1 ?? "const FMagicItem" ? "const FString &"
2> with
2> 
2> KeyType=FMagicItem,
2> ValueType=int32,
2> KeyFuncs=MagicItemMapKeyFuncs_ByClassName<int32>,
2> SetAllocator=FDefaultSetAllocator
2> ]
2> C:\UNR_CODE\UnrealEngine\Engine\Source\Runtime\Core\Public\Containers/Map.h(382): note: ????: ?????????? ????????? "const FMagicItem" ? "const FString"
2> C:\UNR_CODE\UnrealEngine\Engine\Source\Runtime\Core\Public\Containers/Map.h(382): note: ??? ???????? ??????? ?????????? ??? ???????? ?????? ??????????, ??????????? ?????????, ??? ??? ?????? ??????????
2> C:\UNR_CODE\UnrealEngine\Engine\Source\Runtime\Core\Public\Containers/Map.h(381): note: ?? ???????? ???-??? "<??? ??????>" ????? ????? <??? ??????>
2> C:\UNR_CODE\CPP_XPR_COMPILE\Source\CPP_XPR_COMPILE\MapExampleBlueprintLibrary.cpp(122): note: ???????? ????????? ???? ?? ???????? ???? ??? "int32 *TMapBase<KeyType,ValueType,SetAllocator,KeyFuncs>::Find(const FMagicItem &)"
2> with
2> 
2> KeyType=FMagicItem,
2> ValueType=int32,
2> SetAllocator=FDefaultSetAllocator,
2> KeyFuncs=MagicItemMapKeyFuncs_ByClassName<int32>
2> ]
2> C:\UNR_CODE\UnrealEngine\Engine\Source\Runtime\Core\Public\Containers/Map.h(791): note: ???????? ????????? ???? ?? ???????? ???? ????? "TMapBase<KeyType,ValueType,SetAllocator,KeyFuncs>"
2> with
2> 
2> KeyType=FMagicItem,
2> ValueType=int32,
2> SetAllocator=FDefaultSetAllocator,
2> KeyFuncs=MagicItemMapKeyFuncs_ByClassName<int32>
2> ]
2> C:\UNR_CODE\UnrealEngine\Engine\Source\Runtime\Core\Public\Containers/Map.h(898): note: ???????? ????????? ???? ?? ???????? ???? ????? "TSortableMapBase<KeyType,ValueType,SetAllocator,KeyFuncs>"
2> with
2> 
2> KeyType=FMagicItem,
2> ValueType=int32,
2> SetAllocator=FDefaultSetAllocator,
2> KeyFuncs=MagicItemMapKeyFuncs_ByClassName<int32>
2> ]
2> C:\UNR_CODE\CPP_XPR_COMPILE\Source\CPP_XPR_COMPILE\MapExampleBlueprintLibrary.cpp(118): note: ???????? ????????? ???? ?? ???????? ???? ????? "TMap<FMagicItem,int32,FDefaultSetAllocator,MagicItemMapKeyFuncs_ByClassName<int32>>"
2>C:\UNR_CODE\UnrealEngine\Engine\Source\Runtime\Core\Public\Containers/Map.h(382): error C3536: Pair: ?? ????? ?????????? ?? ?????????
2>C:\UNR_CODE\UnrealEngine\Engine\Source\Runtime\Core\Public\Containers/Map.h(384): error C2227: ??????? ??? ?? "->Value" ?????? ????? ?? ? ?????, ???????? ??? ????????? ???? ?? ??????? ?
2> C:\UNR_CODE\UnrealEngine\Engine\Source\Runtime\Core\Public\Containers/Map.h(384): note: ?: int
2>UnrealBuildTool : error : UBT ERROR: Failed to produce item: C:\UNR_CODE\CPP_XPR_COMPILE\Binaries\Win64\UE4Editor-CPP_XPR_COMPILE.dll
2> (see ../Programs/UnrealBuildTool/Log.txt for full exception trace)
2>Total build time: 128,96 seconds (Parallel executor: 0,00 seconds)
2>C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\VC\VCTargets\Microsoft.MakeFile.Targets(44,5): error MSB3075: команда "C:\UNR_CODE\UnrealEngine\Engine\Build\BatchFiles\Build.bat CPP_XPR_COMPILEEditor Win64 Development "C:\UNR_CODE\CPP_XPR_COMPILE\CPP_XPR_COMPILE.uproject" -WaitMutex -FromMsBuild" завершила работу с кодом 5. Убедитесь в наличии достаточных прав для выполнения этой команды.
2>Сборка проекта "CPP_XPR_COMPILE.vcxproj" завершена с ошибкой.
========== Сборка: успешно: 1, с ошибками: 1, без изменений: 2, пропущено: 0 ==========




[/SPOILER]

Sorry for my bad english.