@Acaunt

Как правильно перегрузить шаблонный оператор (метод, функцию), чтобы наследники попадали в нужный?

У меня есть некий класс, который хранит id и несколько методов и наследники, которые меняют некоторые методы (здесь я максимально упростил код, чтобы ничего лишнего не мешало):
...
template<uint8_t i>
struct Id {
    constexpr static uint8_t id = i;
    // какие-то элементы класса с методами
};

struct first : public Id<1> {
    // переопределение методов
};
struct second : public Id<2> {
    // переопределение методов
};

Также есть класс, который имеет внутреннюю структуру работы, которая может немного меняться в зависимости от того какой id был передан классу. Изначально я его реализовал примерно так:
...
struct Class {
    template<class Text>
    Class& operator<<(const Text& text) {
        cout << text << endl;
        return *this;
    }
    
    template<uint8_t i>
    Class& operator<<(const Id<i>& text) {
        cout << (int)i << endl;
        return *this;
    }
};

Но при проверке работы получается так, что классы наследники попадают не в тот оператор и компилятор не может скомпилировать код:
...
int main() {
    Class c;
    c << "hello"; // работает
    
    c << Id<0>();  // работает
    
    c << first(); // не работает
    
    c << second(); // не работает
    
    return 0;
}

Я в принципе примерно понял почему он не туда попадает. Поэтому я написал функцию, которая проверяет является ли объект наследником и переделал оператор и всё заработало:
...
template<class A, uint8_t a, uint8_t end>
constexpr bool check() {
    if constexpr (is_base_of<Id<a>, A>::value) { return true; }
    else if constexpr (a == end) { return false; }
    else {
        return check<A, uint8_t(a + 1), end>();
    }
}

template<class A>
constexpr bool check() {
    if constexpr (is_base_of<Id<0>, A>::value) { return true; }
    else {
        return check<A, 1, 0>();
    }
}

struct Class {
    template<class Text>
    Class& operator<<(const Text& text) {
        if constexpr(check<Text>()) {
            cout << (int)text.id << endl;
        }
        else {
            cout << text << endl;
        }
        return *this;
    }
};

Но выглядит как-то не очень, и к тому же я попробовал вместо uint8_t поставить простой int и тогда компилятор снова отказывался компилировать (в принципе я его понять могу мало кому захочется проверять все возможные int =) )

Как ещё можно решить данную проблему с наследниками?
  • Вопрос задан
  • 101 просмотр
Решения вопроса 1
@Mercury13
Программист на «си с крестами» и не только
Итак, перед нами конфликт первой и второй функции, и надо первую как-то ограничить.

Вариант 1. Концепция Си++20.
template <class T>
concept Printable = requires(T x) {
        std::cout << x;
};

struct Class {
    template<Printable Text>
    Class& operator<<(const Text& text) {
        cout << text << endl;
        return *this;
    }


Вариант 2. Обратная концепция.
template<uint8_t i>
struct Id {
    constexpr static uint8_t id = i;
    using SpecialPrint = void;
    // какие-то элементы класса с методами
};
. . . . .
template <class T>
concept SpecialPrintable = requires {
    typename T::SpecialPrint;
};

struct Class {
    template<class Text>
    Class& operator<<(const Text& text) {
        cout << text << endl;
        return *this;
    }
    
    template <SpecialPrintable Special>
    Class& operator<<(const Special& text) {
        specialPrint(text);        
        return *this;
    }
    
    template<uint8_t i>
    void specialPrint(const Id<i>& text) {
        cout << (int)i << endl;
    }
};


А на 17 без концепций…
template<uint8_t i>
struct Id {
    constexpr static uint8_t id = i;
    using SpecialPrint = void;
    // какие-то элементы класса с методами
};
. . . . .

template<class T, class Dummy = void>
struct IsSpecPrintable { static constexpr bool value = false; };

template<class T>
struct IsSpecPrintable<T, typename T::SpecialPrint> { static constexpr bool value = true; };

struct Class {
    template <class T>
    Class& operator<<(const T& text)
    {
        if constexpr (IsSpecPrintable<T>::value) {
            specialPrint(text);
        } else {
            normalPrint(text);
        }
        return *this;
    }

    template<class Text>
    void normalPrint(const Text& text) {
        cout << text << endl;
    }

    template<uint8_t i>
    void specialPrint(const Id<i>& text) {
        cout << (int)i << endl;
    }
};
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
wataru
@wataru Куратор тега C++
Разработчик на С++, экс-олимпиадник.
Можно попробовать enable_if<> скомбинировать с is_base_of<>. Только нужно все ваши классы Id унаследовать от одного какого-то общего. И во всех наследниках объявлять int поле, которое и брать в теле функции. Только уже не получится этот int сделать параметром шаблона.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы