Специализированный шаблон на основе * внутреннего * типа умного указателя - PullRequest
0 голосов
/ 02 мая 2020

У меня есть класс, который обертывает универсальный c «умный указатель» (может быть unique_ptr, shared_ptr, et c.).

Я пытаюсь специализировать конструктор в Для того, чтобы позвонить в соответствующие make_unique, make_shared, et c. но я не уверен, правильный синтаксис.

Вот идея ( 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>())
{}

Надеюсь, это понятно. Я хочу специализироваться на основе интеллектуального указателя, но мне нужен доступ к базовому типу, который также управляет интеллектуальным указателем.

Это C ++ 14, но меня интересуют идеи из других стандартов, тоже.


РЕДАКТИРОВАТЬ : некоторые похожие вопросы, но которые не совсем применимы в этом случае (или если они применимы, то необходимые преобразования мне не ясны).

Ответы [ 2 ]

1 голос
/ 02 мая 2020

Несмотря на синтаксические ошибки, ваш код не компилируется, потому что вы не можете частично специализировать шаблоны функций, вы можете только частично специализировать шаблоны классов. Вы можете, однако, перегружать функции. Решение этой проблемы с использованием перегрузок функций совсем не просто. Ниже я подробно описал два метода, которые вы можете использовать для решения своей проблемы, второй из которых использует частичную специализацию шаблонов.

Во-первых, если у вас есть компилятор C ++ 17, вы можете использовать if constexpr и шаблонное программирование для получения желаемого эффекта:

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;

};

Это решение не использует частичную специализацию шаблона. Однако, если вы настаиваете на использовании частичной специализации шаблона или у вас нет компилятора c ++ 17, вам придется специализировать весь класс для различных типов указателей:

//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;
};

Использование частичной специализации шаблона Примерно так, может привести к вздутию кода. Если вы решите использовать это решение, вы можете рассмотреть возможность выделения общих методов в базовый класс, от которого наследуются все специализации. Это поможет уменьшить раздувание кода.

0 голосов
/ 02 мая 2020

Мне кажется, я понял это ( живая ссылка ):

Ключ в том, чтобы иметь шаблонный конструктор с enable_if, определяющим, является ли он правильным выбором.

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;

};

Вероятно, есть лучший способ - проверка is_same выглядит несколько неудачно. Но пока достаточно.

...