Генерация виртуальных методов в базовом классе шаблона для необязательного переопределения в классе наследования - PullRequest
0 голосов
/ 20 февраля 2020

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

Код, приведенный ниже, делает именно это:

#include <tuple>
#include <iostream>

struct A {};
struct B {};
struct C {};

template <typename Class, uint16_t tag>
struct def {
        using message_type = Class;
        static constexpr uint16_t class_tag = tag;
};

// (3) adding "constexpr" or "const" causes compilation failure
auto t = std::make_tuple(def<A, 0>(), def<B, 1>(), def<C, 2>());

template <typename T> // (1)
struct base_handler_t {
        virtual void h(T const& t) { std::cout << "base_handler_t\n"; }
};

template <typename ...Ts> // (2) - adding "const" to "std::tuple<Ts...>" in line below makes code work again if "t" is constant
struct base_handler_t<std::tuple<Ts...>> : public base_handler_t<typename Ts::message_type>...{
        using base_handler_t<typename Ts::message_type>::h...;
};

struct service_t : public base_handler_t<decltype(t)> {
        using base_handler_t<decltype(t)>::h;
        void h(B const & b) {
                std::cout << "service_t\n";
        }
};

int main() {
        service_t n;
        n.h(A());
        n.h(B());
}

РЕДАКТИРОВАТЬ ПОСЛЕ ТОГО, ЧТОБЫ НАЙТИ ТОЧНЫЙ И МИНИМАЛЬНЫЙ ПРИМЕР, КОТОРЫЙ РАЗРЫВАЕТ КОД:

Приведенный выше код работает нормально, если введен как есть, но если строка под комментарием (3) (о добавлении constexpr к определению t) изменяется на:

const auto t = std::make_tuple(def<A, 0>(), def<B, 1>(), def<C, 2>());

или

constexpr auto t = std::make_tuple(def<A, 0>(), def<B, 1>(), def<C, 2>());

код не компилируется. Компилятор утверждает, что:

x.cc: In function ‘int main()’:
x.cc:35:16: error: no matching function for call to ‘service_t::h(A)’
   35 |         n.h(A());
      |                ^
x.cc:28:14: note: candidate: ‘void service_t::h(const B&)’
   28 |         void h(B const & b) {
      |              ^
x.cc:28:26: note:   no known conversion for argument 1 from ‘A’ to ‘const B&’
   28 |         void h(B const & b) {
      |                ~~~~~~~~~~^
x.cc:18:22: note: candidate: ‘void base_handler_t<T>::h(const T&) [with T = const std::tuple<def<A, 0>, def<B, 1>, def<C, 2> >]’
   18 |         virtual void h(T const& t) { std::cout << "base_handler_t\n"; }
      |                      ^
x.cc:18:33: note:   no known conversion for argument 1 from ‘A’ to ‘const std::tuple<def<A, 0>, def<B, 1>, def<C, 2> >&’
   18 |         virtual void h(T const& t) { std::cout << "base_handler_t\n"; }
      |                        ~~~~~~~~~^

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

Код начинает работать после изменения в строке ниже (2):

struct base_handler_t<std::tuple<Ts...>> : ...

до

struct base_handler_t<const std::tuple<Ts...>> : ...

Почему это так? Это потому что std::tuple<Ts...> не соответствует const std::tuple<Ts...> точно? Каковы точные правила, которые регулируют этот случай?

Заранее спасибо.

ОРИГИНАЛЬНОЕ ПОЖАЛУЙСТА ДЛЯ ПОМОЩИ:

В оригинальных методах кода базы для каждого типа (A, B и C в примере выше) определены в классе "service_t" вручную. Я попытался решить эту проблему точно так же, как в примере выше. Пока присутствовали все методы, код работал нормально. Как только я закомментировал один метод, я получил сообщение об ошибке, что нет подходящего метода для вызова с последующим списком возможных совпадений. Было найдено совпадение для метода с аргументом std::tuple<def<.... и т. Д. - кажется, что, хотя в моем фрагменте выше все работает нормально (поэтому для каждого типа элемента кортежа генерируется один метод), существует что-то , препятствующее расширению базы кода из соответствующего шаблона (2), и вместо этого он использует шаблон (1).

Я хотел бы услышать любую идею, почему это не получится. Заранее спасибо.

1 Ответ

1 голос
/ 21 февраля 2020

С

auto t = std::make_tuple(def<A, 0>(), def<B, 1>(), def<C, 2>());

decltype(t) равно std::tuple<def<A, 0>, def<B, 1>, def<C, 2>>

Если t соответствует const, квалифицировано: const auto t = /*..*/, то decltype(t) is const std::tuple<def<A, 0>, def<B, 1>, def<C, 2>>.

Так что для base_handler_t<decltype(t)>, base_handler_t<const std::tuple<def<A, 0>, def<B, 1>, def<C, 2>>> соответствует только основное определение шаблона, а не ваша специализация.

Вы можете использовать вместо base_handler_t<std::remove_const_t<decltype(t)>> или base_handler_t<std::decay_t<decltype(t)>> (удалить ссылка и затем cv квалификаторы)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...