Search code examples
c++structtype-traitsunreal-engine5

Specialize trait using type defined in template struct


How can I specialise a trait using a type when it's definition is nested in a template type ?

Here is an example of what I'm doing and what I want:

// A trait that can be specialized
template<typename T>
struct MyTrait
{
    static constexpr int value = 0;
};
//This base case works fine
struct ClassicStruct 
{
    struct ClassicNestedType {};
};
//Inside this template struct I defined a Type
template<typename T> 
struct TemplateStruct
{
    struct DefinedInTemplate
    {
        T val;
    };

};
//=======THIS IS WHAT I WANT TO DO, and it doesn't compile ==========
template<typename T>
struct MyTrait<TemplateStruct<T>::DefinedInTemplate>
{
    static constexpr int value = 589;

};
//This works fine
template<>
struct MyTrait<bool>
{
    static constexpr int value = 4;
};
//perfectly fine too
template<typename T>
struct MyTrait<TemplateStruct<T>>
{
    static constexpr int value = 254;

};
//Another example that is perfectly fine
template<>
struct MyTrait<ClassicStruct::ClassicNestedType>
{
    static constexpr int value = 468;

};

int main()
{
    printf("value of main: %d \n", MyTrait<TemplateStruct<int>>::value);//prints 254
    printf("value of main: %d \n", MyTrait<TemplateStruct<int>::DefinedInTemplate>::value); //I wish it would print 589 here
    printf("value of main: %d \n", MyTrait<ClassicStruct::ClassicNestedType>::value);//prints 468
}

in GCC the compile error is

type/value mismatch at argument 1 in template parameter list for 'template struct MyTrait' 27 | struct MyTrait<TemplateStruct::DefinedInTemplate>

That error is weird, TemplateStruct<T>::DefinedInTemplate is a type no ?

It should be noted that in my real case I can't replace the trait by a concept or something else. This is in UE5 where I must specialise an existing trait. So I can't get around specialising a trait, and I can't really modify that trait, as I'm trying to avoid editing the engine code. This trait is used a lot and I wouldn't be very comfortable touching it.


Solution

  • This is because the T in TemplateStruct<T>::DefinedInTemplate is in a non-deduced context. Given just any type, we can't tell if it is TemplateStruct<T>::DefinedInTemplate for some T directly with existing C++ tools. It may be possible with C++ reflections in future versions.

    A C++20 solution is to make a concept and partially specialise based on that:

    template<typename T>
    concept IsDefinedInTemplateInsideTemplateStruct =
        std::same_as<T, typename TemplateStruct<decltype(T::val)>::DefinedInTemplate>;
    
    template<IsDefinedInTemplateInsideTemplateStruct T>
    struct MyTrait<T>
    {
        static constexpr int value = 589;
    };
    

    A C++11 solution would be to make DefinedInTemplate an alias for some external template:

    namespace impl {
        template<typename T>
        struct TemplateStructInnerClass {
            T val;
        };
    }
    
    template<typename T>
    struct TemplateStruct {
        using DefinedInTemplate = impl::TemplateStructInnerClass<T>;
    };
    
    template<typename T>
    struct MyTrait<impl::TemplateStructInnerClass<T>>
    {
        static constexpr int value = 589;
    };