sameer karjatkar
sameer karjatkar

Reputation: 2067

template function override

I am trying to create a template speacialization for a function which is based on a type

So for eg for the type number I have

template <class ElementType, typename = typename TEnableIf<TNot<TIsSame<ElementType, double>>::Value>::Type>
void WriteExtras(IGLTFJsonWriter &Writer, const FString &Key, const TSharedPtr<FJsonValue> &Value) const
{
    Writer.Write(Key, float(Value->AsNumber()));
    // Perform further processing or write logic for double
}

Now I want to have one more definition but for bool ...but if I try do

template <class ElementType, typename = typename TEnableIf<TNot<TIsSame<ElementType, bool>>::Value>::Type>
void WriteExtras(IGLTFJsonWriter &Writer, const FString &Key, const TSharedPtr<FJsonValue> &Value) const
{
    Writer.Write(Key, Value->AsBool());
    // Perform further processing or write logic for double
}

I get an error Function already defined

I call the function template as

WriteExtras< EJson>(Writer,Key, Pair.Value);

The EJson is an enum

enum class EJson
{
    None,
    Null,
    String,
    Number,
    Boolean,
    Array,
    Object
};

After @Jan suggestion following is my template implementation

template <bool Condition, typename T = void> using EnableIf_t = typename TEnableIf<Condition, T>::type;

template <class ElementType>
auto WriteCustom(IGLTFJsonWriter& Writer, const FString& Key, const TSharedPtr<FJsonValue>& Value) const
    ->EnableIf_t<TNot<TIsSame<ElementType, FString>>::Value>
    //->TEnableIf<TIsSame<ElementType, FString>::Value>
{
    Writer.Write(Key, Value->AsString());
    const FText Title = FText::FromString(TEXT("Template"));
    FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(TEXT("FString")), &Title);
}


template <class ElementType>
auto WriteCustom(IGLTFJsonWriter& Writer, const FString& Key, const TSharedPtr<FJsonValue>& Value) const
    ->EnableIf_t<TNot<TIsSame<ElementType, bool>>::Value>
    //->TEnableIf<TIsSame<ElementType, FString>::Value>
{
    Writer.Write(Key, Value->AsBool());
    const FText Title = FText::FromString(TEXT("Template"));
    FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(TEXT("Bool")), &Title);
}

and when I try to call

WriteCustom<EJson>(Writer,Key, Pair.Value);

it gives error

Upvotes: 1

Views: 145

Answers (1)

Jan Schultke
Jan Schultke

Reputation: 40259

These two functions actually declare the same function:

template <class ElementType,
          class = typename TEnableIf<TNot<TIsSame<ElementType, double>>::Value>::Type>
void WriteExtras(/* same as below */) const;

template <class ElementType,
          class = typename TEnableIf<TNot<TIsSame<ElementType, bool>>::Value>::Type>
void WriteExtras(/* same as above */) const;

The only difference between them is the default argument to a template parameter, but that is not making one of these a separate entity. It's like writing:

int foo(int x = 0); 
int foo(int x = 1);

Doing SFINAE with enable_if correctly

You need to do SFINAE with std::enable_if (or your custom imitation) somewhere else in the function signature, such as parameters, noexcept specifications, return types, etc. For functions, one of the best approaches is to use the return type, because it doesn't change the template parameter list:

// convenience alias (since C++14)
// similar to std::enable_if_t
template <bool Condition, typename T = void>
using EnableIf_t = typename EnableIf<Condition, T>::type;

// note: you could also make a convenience variable template for TIsSame

template <class ElementType>
auto WriteExtras(/* ... */) const
  -> TEnableIf_t<not TIsSame<ElementType, double>::Value>;
// note: TNot is unnecessary, you can just write '!' or 'not' here

template <class ElementType>
auto WriteExtras(/* ... */) const
  -> TEnableIf_t<not TIsSame<ElementType, bool>::Value>;

Alternatively, some people prefer to do the SFINAE part in another non-type template parameter (NTTP):

template <class ElementType, TEnableIf_t<not TIsSame<ElementType, double>::Value, int> = 0>
void WriteExtras(/* ... */) const;

The reason why this works is because the functions would have a different type for that NTTP, so the functions aren't actually the same. It also wouldn't be possible to bypass the constraint by providing a template argument explicitly.

Alternative solutions

However, it's also highly questionable whether you should be using SFINAE here at all. You can achieve the same with if constexpr (C++17):

template <class ElementType>
void WriteExtras(/* ... */) const {
    if constexpr (TIsSame<ElementType, double>::value) {
        // ...
    }
    else if constexpr (TIsSame<ElementType, bool>::value) {
        // ...
    }
    else {
        // TODO: handle this error case somehow
        //       (specifics may depend on language version and compiler)
    }
}

You can also use full specializations, as long as there is only one template parameter:

template <class ElementType> // Inside class
// Primary template is defined as deleted, so only calling
// the specializations is allowed.
void WriteExtras(/* ... */) const = delete;

template <> // Outside class
void JsonWriter::WriteExtras<double>(/* ... */) const { /* ... */ }

// More specializations here ...

Upvotes: 2

Related Questions