domoremath
domoremath

Reputation: 555

Method outside template class with enable_if

I have the following code where I want a template class to check the template value as one of int64_t or unint64_t upon init. The class has a GetValue method that is implemented outside the class.

template <typename T, typename std::enable_if<std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t>>::type>
class Class
{
public:
    Class(T value) noexcept :
        cachedValue(value) {}

    T GetValue() noexcept;

private:
    T cachedValue;
};

template <typename T>
T Class<T>::GetValue() noexcept
{
    // placeholder for now.
    return cachedValue;
}

However, I am running into build errors C2955, C2956, C2976 or something similar from the enable_if even after trying several ideas from other SO posts. How do I get this working?

Upvotes: 0

Views: 272

Answers (3)

Jarod42
Jarod42

Reputation: 217075

Issue with

template <typename T,
          typename std::enable_if<std::is_same_v<T, int64_t>
                               || std::is_same_v<T, uint64_t>>::type>
class Class;

is that is result into

template <typename T_OK, void> class Class;

or

template <typename T_KO, /*Subsitution failure*/> class Class;

Which are both incorrect.

For non-friendly cases, (which might be enough most of the time), you might simply use static_assert:

template <typename T> class Class
{
    static_assert(std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t>);
    /// ...
};

Else there are several variation (I will shorter the condition to cond_v<T>)

  • Common one :( :

    template <typename T, typename = std::enable_if_t<cond_v<T>>>
    class Class
    {
        /// ...
    };
    Class<int64_t> ok;
    Class<float> ko; // doesn't compile, as expected
    // but might be hijacked:
    Class<float, void> hijacked; // compile, unexpected
    
  • Fixed:

    template <typename T, std::enable_if_t<cond_v<T>, int> = 0>
    class Class
    {
        /// ...
    };
    Class<int64_t> ok;
    Class<float> ko; // doesn't compile, as expected
    
  • One which allow customization

    template <typename T, typename = void>
    class Class;
    
    template <typename T>
    class Class<T, std::enable_if_t<cond_v<T>>>
    {
        /// ...
    };
    Class<int64_t> ok;
    Class<float> ko; // incomplete type: doesn't compile, as expected
    
    // user might add additional specialization:
    template <typename T>
    class Class<std::vector<T>, void> {/*..*/};
    

    mostly useful to create customizable traits (as you specialize whole class).

C++20 introduces concept which shorter and simplify syntax:

  • restreint allowed type (there are several equivalent syntax)

    template <typename T> requires(cond_v<T>)
    class Class { /*..*/};
    
  • Allow customization, take best specialization:

    template <typename T>
    class Class;
    
    // Specialization
    template <typename T> requires(cond_v<T>)
    class Class<T> { /*..*/};
    

Upvotes: 1

RedFog
RedFog

Reputation: 1015

there is also a traditional way to make class template SFINAE-friendly before C++20:

template<typename T, typename = void>
class Class; // <1> you can define it here if the conditions are not satisfied.

template<typename T>
class Class<T, typename std::enable_if<std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t>>::type>{
public:
    Class(T value) noexcept :cachedValue(value) {}

    T GetValue() noexcept;

private:
    T cachedValue;
};

int main(){
    Class<int64_t> a = 0; // ok
    Class<uint64_t> b = 0; // ok
    Class<int32_t> c = 0; // incomplete-type. you can define it at <1>.
}

Upvotes: 0

asmmo
asmmo

Reputation: 7090

You can use @WhozCraing method, use a static_assert to provide some message/instruction or use Concepts with C++20, as follows

template<class T>
concept ints64 = std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t>;
template <typename T> requires(ints64<T>)
struct Class{
    Class(T value) noexcept : cachedValue(value) {}
    T GetValue() noexcept;

private:
    T cachedValue;
};

template <typename T>requires(ints64<T>)
T Class<T>::GetValue() noexcept{
    return cachedValue;
}

int main(){
    return Class<int64_t>{0}.GetValue();//return Class<int>{0}.GetValue(); won't compile

}

Upvotes: 0

Related Questions