Search code examples
c++templatessfinae

Class function template definition outside of the class


So i've been coding my own unique_ptr class and i have to handle arrays in a different way that i handle other types.

template <typename TYPE, bool _arr = std::is_array<TYPE>::value>
    class scoped_ptr final {
    private:
        typedef std::remove_extent_t<TYPE> gen;
        gen* m_data;
    public:
        //some methods
        void reset();
    };

template<typename TYPE ,bool _arr>
    inline void scoped_ptr<TYPE, _arr>::reset()
    {
    //some code...
    }

The problem is that i want the reset method to only be avaiable to non array allocations, when using std::enable_if i get the error: "A default template argument cannot be specified on the declaration of a member of a class template outside of its class" despite the code still compiling

template<typename TYPE ,bool _arr>
    class scoped_ptr final {
    public:
        template<typename = typename std::enable_if<!_arr>::type>
        void reset();
    };

template<typename TYPE ,bool _arr>
template<typename = typename std::enable_if<!_arr>::type>
    inline void scoped_ptr<TYPE, _arr>::reset()
    {
    }

I also tryed this, but it also gives an error: "template argument list must match parameter list"

template<typename TYPE ,bool _arr>
    inline void scoped_ptr<TYPE, false>::reset()
    {
    }

Does anybody have an idea on how can i disable this method for arrays? I know i could specialize the class scoped_ptr but i wanted to avoid code duplication. Is there any way to do it?

Edit:

After reading the responses i changed the code to this:

template <typename TYPE, bool is_arr = std::is_array<TYPE>::value>
    class scoped_ptr final {
    private:
        typedef std::remove_extent_t<TYPE> gen;
        gen* m_data;

    public:
        //Some methods

        template<bool _arr = is_arr, typename = typename std::enable_if<!_arr>::type>
        void reset();

    };


    template<typename TYPE, bool is_arr>
    template<bool, typename>
    inline void scoped_ptr<TYPE, is_arr>::reset()
    {
    }

The code compiles with no errors, until i try to call the method:

int main() {

    scoped_ptr<int> ptr = new int;
    ptr.reset();
}

Thats when i get the error: "void scoped_ptr«int,false»::reset(void): could not deduce template argument for «unnamed-symbol»"

But if i write the implementation inside of the class the error goes away. How can i fix this?


Solution

  • If you want to make reset() SFINAE-friendly, make it a fake template:

    template<bool is_arr = _arr, typename = std::enable_if_t<is_arr>>
    void reset();
    

    Note that SFINAE works when a template is instantiated, and the condition should depend on the template parameter. This is not a valid SFINAE construct:

    template<typename = typename std::enable_if<!_arr>::type>
    void reset();
    

    If you don't care about SFINAE-friendliness, use static_assert() inside reset().

    Edit.

    Consider the following simple code as a demonstration of valid and invalid SFINAE:

    template<class T, bool f = std::is_integral_v<T>>
    struct S {
        // template<typename = std::enable_if_t<f>>                 // (invalid)
        template<bool _f = f, typename = std::enable_if_t<_f>>      // (valid)
        void reset() {}
    };
    
    template<class T, typename = void>
    struct has_reset : std::false_type {};
    
    template<class T>
    struct has_reset<T, std::void_t<decltype(std::declval<T>().reset())>> : std::true_type {};
    
    void foo() {
        has_reset<S<int>>::value;
        has_reset<S<void>>::value;
    }
    

    It will fail to compile if your replace the (valid) line with the (invalid) one.

    Edit 2.

    When you define a member function outside the class, you don't repeat default values of template parameters:

    template<class T, bool f>
    template<bool, typename>
    void S<T, f>::reset() { }
    

    Edit 3.

    For some reason (a compiler bug I suppose) MSVC rejects this definition with the error: "Could not deduce template argument for «unnamed-symbol»". It can be fixed by adding a name for the bool parameter:

    template<class T, bool f>
    template<bool _f, typename>
    void S<T, f>::reset() { }
    

    This name should match that in the declaration.