1
votes

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?

1
Try to replace template<bool, typename> with template<bool _arr, typename>. Looks like a bug in Microsoft compiler. - Evg
Wow that helped a lot thx for everything, i also should have noted that im working with Visual Studio 19. Does that bug always happen in this context? What should i be on the lookout for? - Denomycor
These bugs are very unpredictable and there is no universal recipe how to avoid them and how to fix them. When it comes to templates, I've seen many of them in MSVC. I'd suggest to try another compiler (e.g. gcc - see example) and if it compiles the code, fiddle around with it to make it work with MSVC. Adding a redundant parameter name was a pure guess on my part. - Evg

1 Answers

3
votes

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.