Search code examples
c++templatesconstructorenable-if

template class constructor definition with enable_if outside class prototype


There was a question somewhat related to my problem earlier, which dealt with template class, that used std::enable_if on a method, which is declared in class prototype, but the actual implementation is done outside.

Source: function implementation with enable_if outside of class definition

I want to do something similar, but with the class constructor, which I want to define outside template class with std::enable_if metafunction.

template <typename T>
using EnableIfArithmetic = typename std::enable_if<std::is_arithmetic<T>::value, void>::type;

template <typename NumericType>
class SomeClass {
public:
    // constructor definition
    template <typename = EnableIfArithmetic<NumericType>>
    SomeClass() {
        // do some stuff
    }
};

Desired form:

template <typename NumericType>
class SomeClass {
public:
     // constructor declaration
     template <typename = EnableIfArithmetic<NumericType>>
     SomeClass();
};

// constructor definition
template <typename NumericType>
template <typename = EnableIfArithmetic<NumericType>>
SomeClass<NumericType>::SomeClass() {
        // constructor implementation
}

But I cannot get it right, without compiling error. What am I doing wrong?


Solution

  • Default values for the template arguments should not be repeated in the definitions. For example:

    template<typename N>
    struct S {
        template<typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
        S(T);
    };
    
    template<typename N>
    template<typename T, typename>
    S<N>::S(T) { }
    

    The way you use SFINAE is not correct: EnableIfArithmetic should depend on some deduced type (in the same template). Please refer to this question. For example:

    template<typename T = N, typename = EnableIfArithmetic<T>>
    S() { }
    

    Otherwise, a hard fail will occur:

    error: no type named 'type' in 'struct std::enable_if'

    If you want to disable the default constructor for some types N, you can also use static_assert inside the constructor. However, it won't be SFINAE friendly.

    template<typename N>
    struct S1 {
    public:
        template<typename T = N, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
        S1() { }
    };
    
    template<typename N>
    struct S2 {
    public:
        S2() { static_assert(std::is_arithmetic_v<N>); }
    };
    
    static_assert(std::is_default_constructible_v<S1<int>>);
    static_assert(!std::is_default_constructible_v<S1<void>>);
    
    static_assert(std::is_default_constructible_v<S2<int>>);
    static_assert(std::is_default_constructible_v<S2<void>>);