Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > C/C++: Общие вопросы > шаблонный конструктор шаблонного класса да еще и с разным числом аргументов


Автор: Dushevny 30.03.20, 20:48
имеется шаблонный класс:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    template<typename payload>
    class message
    {
    public:
        template<typename source>
        message(int param1, source param2);
        
    private:
        payload Payload;
    };

для разных специализаций этого шаблона я пишу разные конструкторы:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    template<> template<typename source>
    message<a>::message(int param1, source param2)
    {
     
    }
     
    template<> template<>
    message<b>::message(int param1, bool param2)
    {
     
    }

но для одной из специализаций мне потребовался дополнительный параметр конструктора. Я, конечно, могу в исходный шаблон добавить второй конструктор с тремя аргументами, но считаю это не лучшим решением, потому что завтра мне для какой-то еще специализации может понадобится еще один аргумент и придется дописывать еще один конструктор. Попытался использовать variadic templates:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    ....
    public:
        template<typename ...Args>
        message(int param1, Args ...args);
    ....


почти все хорошо: я могу описать полную специалзацию с любым количеством параметров:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    template<> template<>
    message<с>::message(int param1, int param2, bool param3)
    {
     
    }

но мне нужно, чтобы один из параметров оставался шаблонным, то есть получить что-то вроде
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    template<> template<typename source>
    message<d>::message(int param1, source param2)
    {
     
    }
    template<> template<typename source>
    message<e>::message(int param1, int param2, source param3)
    {
     
    }
Как это сделать? Если такое вообще возможно...

Автор: Qraizer 30.03.20, 21:24
Цитата Dushevny @
Я, конечно, могу в исходный шаблон добавить второй конструктор с тремя аргументами, но считаю это не лучшим решением, потому что завтра мне для какой-то еще специализации может понадобится еще один аргумент и придется дописывать еще один конструктор.
Ты можешь его объявить, но реализовывать его будет только та специализация, которая сочтёт это нужным ей. Вызов такого конструктора для любой другой специализации вызовет ошибку линковки.
Цитата Dushevny @
Попытался использовать variadic templates:
Я бы не стал считать это решение лучшим предыдущего. Скорее наоборот, оно неинуитивнее, ибо непонятнее зачем оно нужно. В специальном конструкторе смысл видится яснее. И скорее даже будет меньше вопросов "зачем он тут" по сравнению "что это вообще такое", если увидят вариадик. Но это субъективно, и зависит от конкретики ситуации.

Добавлено
P.S. Вообще же, это какая-то странная задача, когда надо для каждой пары шаблонных параметров мутить отдельные специализации. Обычно шаблоны используются с точностью до наоборот. Ты точно не мультиметоды пытаешься изобрести?

Автор: Dushevny 30.03.20, 21:35
Qraizer, спасибо. Пожалуй, вы правы и оба решения хуже.
Как говорится "стоило написать вопрос и тут же сам нашел ответ". Он, правда, не лучше первых двух, но в данной задаче меня устраивает - сделал наследника от нужной специализации, в конструкторе наследника обрабатываю "лишний" аргумент, в конструкторе специализации - два общих аргумента.

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

Добавлено
Цитата Qraizer @
Вообще же, это какая-то странная задача, когда надо для каждой пары шаблонных параметров мутить отдельные специализации. Обычно шаблоны используются с точностью до наоборот. Ты точно не мультиметоды пытаешься изобрести?
Про мультиметоды даже не слышал, сейчас почитаю. Задача проста как грабли - имеется куча разных сообщений. Все они наследники одного базового класса, в котором определены виртуальные функции работы с этими сообщениями - распечатать, сравнить, отослать дальше. Каждый тип сообщения во входном потоке закодирован по-своему, конструктор разбирает входной поток и заполняет поля сообщения. А параметр этого потока шаблонный, потому что работа с этими сообщениями используется в разных программах и в одной из них входной поток - массив байтов, в других - массив классов (для каждого типа сообщений свой класс), для которых переопределен оператор приведения к байту.

Автор: OpenGL 31.03.20, 05:58
Выглядит как будто тебе нужна перегрузка, а не специализация.

Автор: Dushevny 31.03.20, 08:15
Фактически, да. Но объявление всех классов-наследников одинаковы (кроме конструктора в некоторых случаях), поэтому я обернул их в шаблон.

Впрочем, ночью понял, что перемудрил с решением и буду все переделывать.

Автор: Qraizer 31.03.20, 13:10
Пожалуй, шаблоны тут вообще не нужны. Достаточно обычного полиморфизма.

Автор: Dushevny 31.03.20, 14:49
хорошо. Допустим, вот типы сообщений:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    struct a { int a; };
    struct b { char b; };
    struct c { long c; };

вот базовый класс:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class generic_msg
    {
    public:
        virtual void show() const = 0;
        virtual void send() const = 0;
    };

вот решение с полиморфизмом:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    class msg_a : public generic_msg
    {
    public:
        void show() const override;
        void send() const override;
    private:
        a   Payload;
    };
     
    class msg_b : public generic_msg
    {
    public:
        void show() const override;
        void send() const override;
    private:
        b   Payload;
    };
     
    class msg_c : public generic_msg
    {
    public:
        void show() const override;
        void send() const override;
    private:
        c   Payload;
    };

Здесь их три, по факту - несколько десятков

вот решение с шаблонами:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    template<typename payload>
    class msg : public generic msg
    {
    public:
        void show() const override;
        void send() const override;
    private:
        payload   Payload;
    };

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

Добавлено
Шаблоны ведь именно для этого и придуманы.

Моя архитектурная ошибка была в том, что я заполнение полей сообщений возложил на конструктор, а надо в шаблоне сделать конструктор перемещения, принимающий объект типа payload. А уже этот объект создавать другим набором классов, который свой для каждой программы, использующей работу с этими сообщениями.

Автор: Qraizer 31.03.20, 23:58
Цитата Dushevny @
Шаблоны ведь именно для этого и придуманы.
Не совсем для этого. Но это неважно. Архитектурная ошибка... даже скорее недочёт, заключается в том, что у тебя по факту два интерфейса, а реализовал ты как интерфейс лишь один, а другой жёстко в него вшит и не выделен как отдельная параметризируемая для него сущность. Для payload тоже надо было бы предусмотреть интерфейс с get()/set(), который бы абстрагировали его репрезентацию. <iostream> помнишь? std::basic_ios<> отдельно, а std::basic_streambuf<> отдельно. Оба находятся в тесной связи, но пульзуют друг друга полиморфно.

Добавлено
P.S. К слову сказать, тут мультиметод вполне мог бы всплыть как синергия между двумя независимыми деревьями.

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)