Могу ли я как-то раскрыть параметр шаблона шаблона?

Я хотел бы предоставить надлежащий механизм клонирования для различных иерархий классов. Это кажется разумной мыслью, и я собрал базовое решение с использованием CRTP для реализации необходимых функций clone() в производных классах.

Я также создал шаблон с аргументом шаблона шаблона, чтобы политики могли контролировать хранение/владение клоном:

    template <typename base, typename derived>
    struct clone_raw
    {
        typedef derived * return_type;
        static return_type clone(const base * original) { return new derived(static_cast<const derived &>(*original)); }
    };

    template <typename base, typename derived>
    struct clone_shared
    {
        typedef std::shared_ptr<derived> return_type;
        static return_type clone(const base * original) { return std::make_shared<derived>(static_cast<const derived &>(*original)); }
    };

    template <typename base, typename derived>
    struct clone_unique
    {
        typedef std::unique_ptr<derived> return_type;
        static return_type clone(const base * original) { return std::make_unique<derived>(static_cast<const derived &>(*original)); }
    };

    // derived class CRTP without base CRTP helper
    template <typename base, typename derived, template <typename,typename> typename policy = clone_raw>
    class clonable : public base
    {
    public:
        // define our derived's parent class
        using parent = clonable<base, derived, policy>;

        // constructor forwarding (enable all constructors)
        using base::base;

        // clone using policy
        auto clone() const
        {
            return policy<base, derived>::clone(this);
        }
    };

Это работает достаточно хорошо, поскольку каждый производный класс должен использовать CRTP для вызова вышеуказанного механизма.

        class Example
        {
        public:
            virtual std::shared_ptr<Example> clone() const = 0;
            virtual void explain() const = 0;
        };

        class Ex1 : public clonable<Example, Ex1>
        {
        public:
            Ex1(const char * text) : m_text(text) {}
            void explain() const override { std::cout << m_text; }
        private:
            const char * m_text;
        };

        class Ex2 : public clonable<Ex1, Ex2>
        {
        public:
            Ex2(const char * text, const char * extra) : parent(text), m_extra(extra) {}
            void explain() const override { parent::explain(); std::cout << " " << m_extra; }
        private:
            const char * m_extra;
        };

Однако это оставляет базовому классу необходимость реализации корневого виртуального метода clone(), а это означает, что везде в иерархии политика клонирования должна указываться снова и снова. Это, конечно, анафема передовой практики/здравого смысла/эффективности/правильности по умолчанию/и т.д.

Итак, я подумал, как насчет того, чтобы сделать два шаблона CRTP, которые работают вместе, один для предоставления базовому классу исходного виртуального clone() с правильной подписью, а затем производный CRTP, который использует свой родительский класс для определения правильной политики клонирования для использования. , так что нужно указать политику только один раз, в корневом классе, и все производные классы будут реализовывать правильное переопределение clone(), разумно определяя для себя, какая политика используется базовым классом.

Однако я не могу понять, как предоставить шаблон шаблона политики для производных шаблонов CRTP, чтобы им не нужно было принимать какие-либо явные параметры политики - для реализации идеи дизайна.

    // base class CRTP
    template <typename base, template <typename, typename> typename policy = clone_raw>
    class clonable_base : base
    {
    public:
        // define our derived's parent class
        using parent = clonable_base<base, policy>;

        // constructor forwarding (enable all constructors)
        using base::base;

        using clone_policy = policy<base, base>;
        using clone_type = typename clone_policy::return_type;

        // clone using policy
        virtual clone_type clone() const = 0
        {
            return clone_policy::clone(this);
        }
    };

Итак, вопрос на миллион долларов здесь: как сделать шаблон шаблона policy доступным для следующего производного CRTP:

    // derived class CRTP with base CRTP helper
    template <typename base, typename derived>
    class clonable_derived : public base
    {
    public:
        // define our derived's parent class
        using parent = clonable_derived<base, derived>;

        // constructor forwarding (enable all constructors)
        using base::base;

        using policy = base::policy; // ???

        using clone_policy = policy<base,derived>;
        using clone_type = typename clone_policy::return_type;

        // clone using policy
        clone_type clone() const override
        {
            return clone_policy::clone(this);
        }
    };

Кажется, все на месте, но я не понимаю, как открыть шаблон шаблона политики, чтобы производные клонируемые типы могли получить к нему доступ для создания экземпляра соответствующей политики для своих базовых/производных пар?!!


person Mordachai    schedule 13.02.2017    source источник


Ответы (2)


Используйте шаблон псевдонима:

template <class A, class B>
using policy = typename base::template policy<A,B>;
person Barry    schedule 13.02.2017
comment
Это выглядит обнадеживающе. В настоящее время моя голова взрывается при переопределении возвращаемого типа виртуальной функции, который отличается и не является ковариантным - если я использую свой код, как написано выше. Не хватает чего-то элементарного. Но спасибо :D - person Mordachai; 13.02.2017

Хорошо - ответ Барри правильный - псевдоним шаблона абсолютно правильный инструмент.

У меня были различные проблемы в моем исходном коде, в том числе проблемы, связанные с тем, что нельзя изменить возвращаемый тип виртуального, если он не является ковариантным, и unique_ptr и shared_ptr, похоже, не поддерживают ковариантность (по крайней мере, не в VC++ 2015 Update 3) .

Итак, вот рабочий код — и я, безусловно, открыт для предложений по улучшению!

    // cloning policies:
    //  clone_raw       = new
    //  clone_shared    = std::shared<>
    //  clone_unique    = std::unique<>

    template <class base, class derived>
    struct clone_raw
    {
        using return_type = base *;
        static return_type clone(const base * original) { return new derived(static_cast<const derived &>(*original)); }
    };

    template <class base, class derived>
    struct clone_shared
    {
        typedef std::shared_ptr<base> return_type;
        static return_type clone(const base * original) { return std::make_shared<derived>(static_cast<const derived &>(*original)); }
    };

    template <class base, class derived>
    struct clone_unique
    {
        typedef std::unique_ptr<base> return_type;
        static return_type clone(const base * original) { return std::make_unique<derived>(static_cast<const derived &>(*original)); }
    };

    // base class CRTP
    template <class base, template <class, class> typename policy = clone_raw>
    class clonable_base
    {
    public:

        // define our derived's parent class
        using parent = clonable_base<base, policy>;

        template <class derived>
        using policy_alias = policy<base, derived>;

        using clone_policy = policy_alias<base>;
        using clone_type = typename clone_policy::return_type;

        // clone using policy
        virtual clone_type clone() const = 0;
    };

    // derived class CRTP with base CRTP helper
    template <typename base, typename derived>
    class clonable_derived : public base
    {
    public:
        // define our derived's parent class
        using parent = clonable_derived<base, derived>;

        // constructor forwarding (enable all constructors)
        using base::base;

        template <class derived>
        using policy_alias = typename base::template policy_alias<derived>;

        using clone_policy = typename policy_alias<derived>;
        using clone_type = typename clone_policy::return_type;

        // clone using policy
        clone_type clone() const override
        {
            return clone_policy::clone(this);
        }
    };

А вот и банальный тест:

        class Example : public clonable_base<Example, clone_shared>
        {
        public:
            virtual void explain() const = 0;
        };

        class Ex1 : public clonable_derived<Example, Ex1>
        {
        public:
            Ex1(const char * text) : m_text(text) {}
            void explain() const override { std::cout << m_text; }
        private:
            const char * m_text;
        };

        class Ex2 : public clonable_derived<Ex1, Ex2>
        {
        public:
            Ex2(const char * text, const char * extra) : parent(text), m_extra(extra) {}
            void explain() const override { parent::explain(); std::cout << " " << m_extra; }
        private:
            const char * m_extra;
        };

int main()
{
    Ex1 ex1("example 1");
    Ex2 ex2("example 2", "this is derived derived example");
    auto clone = ex2.clone();

    ex1.explain();
    std::cout << std::endl;
    ex2.explain();
    std::cout << std::endl;
    clone->explain();
    std::cout << std::endl;

    return 0;
}
person Mordachai    schedule 13.02.2017