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


Автор: AZote 20.02.19, 11:38
Есть базовый шаблонный класс и его шаблонные наследники:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    template<class Derived, class Measure>
    class IntervalMeasurer
    {
    protected:
        std::vector<Measure> measures;
        uint32_t interval = 0;
    public:
        void setMeasureInterval(uint32_t measure_interval) {
            this->interval = measure_interval;
            this->measures.reserve(uint32_t(max_period) / measure_interval);
        }
        void addMeasure(Measure value) {
            if (measures.size() == measures.capacity()) {
                measures.erase(measures.begin());
            }
            measures.push_back(value);
        }
        Measure getAvg1() const {
            return static_cast<Derived*>(this)->getAverage(1*60);
        }
        Measure getAvg5() const {
            return static_cast<Derived*>(this)->getAverage(5*60);
        }
        Measure getAvg15() const {
            return static_cast<Derived*>(this)->getAverage(15*60);
        }
    };
     
    template<class Measure>
    class IntervalInstantMeasurer : public IntervalMeasurer<IntervalInstantMeasurer<Measure>, Measure>
    {
    public:
        Measure getAverage(uint32_t period) const {
            uint32_t count = std::min(period/interval, measures.size());
            Measure sum = std::accumulate(measures.rbegin(), measures.rbegin()+(count-1), Measure());
            return sum / count;
        }
    };
     
    template<class Measure>
    class IntervalGrowingMeasurer : public IntervalMeasurer<IntervalGrowingMeasurer<Measure>, Measure>
    {
    public:
        Measure getAverage(uint32_t period) const {
            uint32_t count = std::min(period/interval, measures.size());
            if (count >= 2) {
                Measure current_measure = measures.back();
                Measure previous_measure = measures[measures.size() - count];
                return (current_measure - previous_measure) / period;
            }
            return Measure();
        }
    };


Почему компилятор выдает ошибки рода:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    error: ‘interval’ was not declared in this scope
    error: ‘measures’ was not declared in this scope

? :wall:

Автор: Dushevny 20.02.19, 12:37
Надо явно указывать базовый класс:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    Measure current_measure = IntervalMeasurer<IntervalGrowingMeasurer<Measure>, Measure>::measures.back();

Автор: AZote 20.02.19, 13:41
Цитата Dushevny @
Надо явно указывать базовый класс:

А знаете что, с this прокатило
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
        Measure getAverage(uint32_t period) const {
            uint32_t count = std::min(size_t(period/this->interval), this->measures.size());
            Measure sum = std::accumulate(this->measures.rbegin(), this->measures.rbegin()+(count-1), Measure());
            return sum / count;
        }

:blink:

Автор: Wound 20.02.19, 14:20
Могу предположить что когда он доходит до компиляции твоих пременных, он просто про них не знает, так как класс базовый у тебя шаблонный, и он судя во всему до этого момента еще не инстанцирован. Поэтому и ругнулся.
А то что ты написал там this и у тебя заработало, так это называеться Two phase lookup

Добавлено
Вот тут еще про это даже посмотреть можно:
https://www.youtube.com/watch?v=LZPHrYZ_dBw

Добавлено
Тут по русски можно почитать кусочек: https://books.google.ru/books?id=Gn11DwAAQB...1%D0%BA&f=false

Автор: Qraizer 20.02.19, 14:38
Цитата Wound @
Могу предположить что когда он доходит до компиляции твоих пременных, он просто про них не знает, так как класс базовый у тебя шаблонный, и он судя во всему до этого момента еще не инстанцирован.
Не, не поэтому. Был или не был инстанцирован, неважно, это уже вторая фаза, и ежели чё, тут же и сынстанцируется. Ошибка же возникает ещё на первой фазе связывания имён. И interval, и measures не обозначены как зависимые имена, т.к. для них отсутствует квалификация. По правилам связывания имён независимые имена обязаны быть окончательно связаны на первой фазе, в противном случае велик риск получить сайд-эффекты а-ля препроцессор. Т.с. компилятор защищает автора базового шаблона, который отсутствием квалификации для независимых имён зафиксировал их смысл, от неожиданного контекста в точке инстацирования, который пользователь авторского шаблона невольно мог наполнить "неподходящими" именами (на чём макросоподобный сайд-эффект как раз и проявился бы).
Поэтому компилятор первым делом смотрит в текущую область видимости, не видит их и смотрит в окаймляющую, каковой является глобальная. В область видимости базового класса, даже притом, что он определён, он не заглядывает, т.к. у него нет никакой информации, что туда надо смотреть, независимые имена там точно не должны быть найдены. Кроме того, он и не должен туда смотреть, т.к. в точке инстацирования к первичному шаблону базового класса могут быть добавлены специализации (размещённые уже после определения производных классов, такой же сайд-эффект в общем-то), поэтому, если б он туда смотрел, собранная на первой фазе информация об именах может быть не полной, искажённой сайд-эффектами или даже ошибочной.
Исправление ситуации достигается введением зависимости для interval и measures посредством явной квалификации. И это правильно, т.к. т.о. пользователь шаблона явно декларирует их зависимость от шаблонных параметров, что даёт право компилятору отложить их окончательное связывание до точки инстанцирования. Можно квалифицирующим маршрутом явно указать базовый класс, как у Dushevny, можно и просто посредством this, который неявно подразумевает полную область видимости инстанцируемого экземпляра, включая и унаследованные сущности. В обоих случаях эти имена оказываются явно зависимыми от контента базового класса, а значит их окончательное связывание должно быть отложено до второй фазы. Что компилятор и делает, и у него всё срастается, т.к. это именно то, что хотел пользователь шаблона.

Добавлено
По первой ссылке Wound как раз приводится хороший пример сайд-эффекта.

Автор: AZote 21.02.19, 21:25
Кстати, не подскажете, что кошернее, использовать CRTP, как я сделал выше, или сделать виртуальную функцию в шаблонном классе, например так:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    template<class Measure>
    class IntervalMeasurer
    {
    protected:
        std::vector<Measure> measures;
        uint32_t interval = 0;
    public:
        void addMeasure(Measure value) {
            measures.push_back(measure);
        }
        virtual Measure getAverage(uint32_t period) const = 0;
     
        Measure getAvg1() const {
            return getAverage(1*60);
        }
        Measure getAvg5() const {
            return getAverage(5*60);
        }
    };
     
    template<class Measure>
    class InstantIntervalMeasurer : public IntervalMeasurer<Measure>
    {
    public:
        Measure getAverage(uint32_t period) const override {
            return Measure();
        }
    };

Автор: ЫукпШ 22.02.19, 09:25
Цитата AZote @
.. компилятор выдает ошибки рода:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    error: ‘interval’ was not declared in this scope
    error: ‘measures’ was not declared in this scope

? :wall:

А если попробовать приблизительно так:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    template<class Measure>
    class IntervalInstantMeasurer : public IntervalMeasurer<IntervalInstantMeasurer<Measure>, Measure>
    {
     
    protected:
        using IntervalMeasurer<IntervalInstantMeasurer<Measure>, Measure>::std::vector<Measure> measures;
        using IntervalMeasurer<IntervalInstantMeasurer<Measure>, Measure>::uint32_t interval;
     
    public:
     
    // ...
    // ...
     
    };

Автор: Олег М 22.02.19, 10:50
Цитата AZote @
Кстати, не подскажете, что кошернее, использовать CRTP, как я сделал выше, или сделать виртуальную функцию в шаблонном классе, например так:

Виртуальная функция нужна если ты планируешь делать так

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    IntervalMeasurer<Measure> *p = new InstantIntervalMeasurer<Measure>();
    p->getAverage(...);


Тогда неплохо было бы добавить ещё и вертуальный деструктор.

Если не планируешь, то лучше без виртульных методов

Автор: Qraizer 22.02.19, 15:12
Цитата AZote @
Кстати, не подскажете, что кошернее, использовать CRTP, как я сделал выше, или сделать виртуальную функцию в шаблонном классе... ?
Стандартный полиморфизм выгоден, когда реализация задаётся, а то и меняется, т.е. не фиксирована, в ран-тайм. Если такой задачи нет, то статический полиморфизм на шаблонах выгоднее. Ты по-прежнему имеешь возможность легко и просто менять реализацию, только не в ран-тайм, а в перед компиляцией, и при этом не имеешь потерь в производительности от позднего связывания.

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