简体   繁体   中英

Specialize template based on *inner* type of a smart pointer

I have a class which wraps a generic "smart pointer" (could be unique_ptr , shared_ptr , etc.).

I am trying to specialize the constructor in order to call the appropriate make_unique , make_shared , etc. but I'm not sure the right syntax.

Here is the idea ( live link ):

template <class SmartPtr>
struct Holder {

    typedef typename SmartPtr::element_type value_type;

    Holder()
        : m_ptr(new value_type) // The fallback implementation calls 'new' directly.
    {}

private:

    SmartPtr m_ptr;

};

// THIS DOES NOT COMPILE
// Notice the "T" type, which is not declared anywhere.
// How do I declare this sort of thing properly?
template<>
Holder<shared_ptr<T>>::Holder
    : m_ptr(make_shared<T>())
{}

Hopefully that is clear. I want to specialize based on the smart pointer, but I need access to the underlying type that the smart pointer managers, too.

This is C++14, but I'm interested in ideas from other standards, too.


EDIT : Some similar questions, but which don't quite apply in this case (or if they do, the transformation needed is not clear to me).

Disregarding the syntactical errors, the reason your code is not compiling, is because, you cannot partially specialise function templates, you can only partially specialise class templates. You can, however, overload functions. Solving this problem using function overloads is not easy at all. I've detailed two techniques below, which you can use to solve your problem, the second of which utilises partial template specialisation.

First, assuming you have a C++17 compiler, you can use if constexpr and template programming to get the desired effect:

template <class SmartPtr>
struct Holder {

    //this is a nested helper struct, which will allow us to deduce at compile-time
    //    whether SmartPtr is a std::unique_ptr or not, using function overloading
    template<typename T>
    struct is_unique_ptr{
        template<typename TT>
        constexpr static std::true_type test(std::unique_ptr<TT>*);

        constexpr static std::false_type test(...);

        using type = decltype(test(std::declval<T*>()));
        constexpr static bool value = type::value;
    };


    //this is a nested helper struct, which will allow us to deduce at compile-time
    //    whether SmartPtr is a std::shared_ptr or not, using function overloading
    template<typename T>
    struct is_shared_ptr{
        template<typename TT>
        constexpr static std::true_type test(std::shared_ptr<TT>*);

        constexpr static std::false_type test(...);

        using type = decltype(test(std::declval<T*>()));
        constexpr static bool value = type::value;
    };

    typedef typename SmartPtr::element_type value_type;

    //default constructor will conditionally construct m_ptr depending on its type
    Holder(){ 
        if constexpr(is_unique_ptr<SmartPtr>::value){
            m_ptr = std::make_unique<value_type>();
        } else if constexpr(is_shared_ptr<SmartPtr>::value){
            m_ptr = std::make_shared<value_type>();
        } else {
            m_ptr = new value_type{};
        }
    }

private:

    SmartPtr m_ptr;

};

This solution does not use partial template specialisation. If however you are insistent on using partial template specialisation, or you do not have a c++17 compiler, you will have to specialise the entire class for the different pointer types:

//generic class Holder (identical to your initial implementation)
template <class SmartPtr>
struct Holder {

    typedef typename SmartPtr::element_type value_type;

    Holder()
        : m_ptr(new value_type){
    }

private:

    SmartPtr m_ptr;

};

//this partial specialisation will be selected if template parameter is a std::unique_ptr 
template<typename T>
struct Holder<std::unique_ptr<T>>{
    using value_type = T;

    Holder() : m_ptr(std::make_unique<value_type>()){

    }

    private:
        std::unique_ptr<T> m_ptr;

};

//this partial specialisation will be selected if template parameter is a std::unique_ptr 
template<typename T>
struct Holder<std::shared_ptr<T>>{
    using value_type = T;

    Holder() : m_ptr(std::make_shared<value_type>()) {
    }

    private:
        std::shared_ptr<T> m_ptr;
};

Using partial template specialisation, like so, can lead to code bloat. If you decide to use this solution, you may want to consider factoring out common methods into a base class, from which all the specialisations inherit. This will help reduce code bloat.

I think I figured this out ( live link ):

The key is to have a templated constructor with enable_if determining if it is a valid choice.

template <class SmartPtr>
struct Holder {

    typedef typename SmartPtr::element_type value_type;

    Holder()
        : m_ptr(new value_type)
    {}

    template <class T = value_type>
    Holder(std::enable_if_t<  std::is_same<shared_ptr<T>,SmartPtr>::value  >* = 0)
        : m_ptr(make_shared<T>())
    {}

private:

    SmartPtr m_ptr;

};

There is probably a better way — the is_same check seems a bit hacky. But good enough for now.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM