Страницы

Поиск по вопросам

вторник, 17 декабря 2019 г.

Создание рекурсивного шаблона

#cpp #cpp11 #шаблоны_с++ #метапрограммирование


У меня есть семейство параметризованных функций, типа:

template
struct SomeFunc {
    static void *call(void *arg) { return arg; }
};


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

В очень примитивной реализации это выглядит так:

template class F>
void *choose_from(void *arg, char run_time_type_of_arg) {
    switch (run_time_type_of_arg) {
        case 'i': return F::call(arg);
        case 'l': return F::call(arg);
        case 'd': return F::call(arg);
        default:  return nullptr;
    }
}


Вызывается это очень удобно:

void *test_some_func(void *arg, char run_time_type_of_arg) {
    return choose_from(arg, run_time_type_of_arg);
}


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

Поэтому я хочу переписать эту конструкцию с использованием type traits и более общих
шаблонов на их основе:

template struct TypeTrait { const static char value = 0; };
template<> struct TypeTrait { const static char value = 'i'; };
template<> struct TypeTrait { const static char value = 'l'; };
template<> struct TypeTrait { const static char value = 'd'; };

template class F, typename T1>
struct ChooseFrom1 {
    inline static void *call(void *arg, char run_time_type_of_arg) {
        if (run_time_type_of_arg == TypeTrait::value)
            return F::call(arg);
        else
            return nullptr;
    }
};

template class F, typename T1, typename T2>
struct ChooseFrom2 {
    inline static void *call(void *arg, char run_time_type_of_arg) {
        if (run_time_type_of_arg == TypeTrait::value)
            return F::call(arg);
        else
            return ChooseFrom1::call(arg, run_time_type_of_arg);
    }
};


За счет встраивания функций все вызовы полностью удаляются на этапе компиляции, и
в результате получается машинный код, почти полностью идентичный первой реализации
на switch/case (я проверял листинги - все так).
Но у меня не получилось рекурсивного шаблона (как видно ChooseFrom1 и ChooseFrom2
имеют разные имена).
Вызывать это можно, например, так (совершенно не удобно):

void *test_some_func_2(void *arg, char run_time_type_of_arg) {
    auto a = ChooseFrom1::call(arg, run_time_type_of_arg);
    if (a) return a;
    return ChooseFrom2::call(arg, run_time_type_of_arg);
}


В идеале я хочу получить один общий шаблон, который можно было бы вызывать так:

void *test_some_func_ideal(void *arg, char run_time_type_of_arg) {
    return ChooseFrom::call(arg, run_time_type_of_arg);
}


Но все попытки совместить ChooseFrom1 и ChooseFrom2 ... ChooseFromN в один рекурсивный
шаблон (один обобщенный шаблон для самой рекурсии плюс одна его специализация для остановки
рекурсии) наталкиваются на ошибки компиляции.

Буду благодарен за любые советы по реализации данной идеи или обоснование теоретической
невозможности подобной реализации.
    


Ответы

Ответ 1



Возможно, вам нужно это: template class F, typename... Types> struct ChooseFrom { }; template class F, typename T1, typename... TRest> struct ChooseFrom { inline static void *call(void *arg, char run_time_type_of_arg) { if (run_time_type_of_arg == TypeTrait::value) return F::call(arg); else return ChooseFrom::call(arg, run_time_type_of_arg); } }; template class F> struct ChooseFrom { inline static void *call(void *arg, char run_time_type_of_arg) { return nullptr; } }; С другой стороны, я бы на вашем месте постарался не доводить до передачи типа через эмуляцию typeid в рантайме, а протянуть тип как параметр шаблона в программе. При этом можно было бы по идее обойтись вовсе без ChooseFrom, и использовать прямо SomeFunc::call.

Комментариев нет:

Отправить комментарий