Я пытаюсь написать небольшой интерпретатор сценариев, который можно расширять с помощью C ++. Для этого обработчики функций вставляются в таблицу диспетчеризации.
Чтобы просто ответить на мой вопрос, тип обработчика определяется следующим образом (в реальном коде он содержит параметры для списка аргументов и возвращаемого типа):
// Handler type
using function_type = void(int);
На данный момент таблица диспетчеризации представляет собой простую неупорядоченную карту с искаженным именем (своего рода) в качестве ключа для реализации перегрузки:
// Dispatch table
std::unordered_map<std::string, function_type*> fn_handlers;
Методы добавляются в эту таблицу либо прямыми, например, как простой метод, который принимает два аргумента типа int
(operator+ii
это название для этого случая в моем случае):
fn_handlers["operator+ii"] = my_add_handler;
Тем не менее, многие обработчики, особенно те, которые связаны с базовой математикой, принимают различные аргументы, все комбинации int
а также double
будет действительным, уступая 4 методам и 4 записям таблицы отправки. Поэтому я решил реализовать эти методы с помощью шаблонов.
Чтобы привести пример, это может быть в основном это (опять же просто):
template<class A, class B>
void my_add_handler(int /* simplified... */)
{
// Something here A and B are needed
}
Таблица отправки затем заполняется так:
fn_handlers["operator+ii"] = my_add_handler<int,int>;
fn_handlers["operator+di"] = my_add_handler<double,int>;
fn_handlers["operator+id"] = my_add_handler<int,double>;
fn_handlers["operator+dd"] = my_add_handler<double,double>;
Еще много набрать, но пока все нормально. В любом случае, поскольку существует очевидная корреляция между параметрами шаблона и сигнатурой метода (искаженное имя), я попытался автоматизировать это, чтобы вы могли написать (управление именами параметров будет сделано внутри handler_provider :: add):
handler_provider<int, int>::add<my_add_handler>("operator+");
handler_provider<double, int>::add<fn_add_handler>("operator+");
handler_provider<int, double>::add<fn_add_handler>("operator+");
handler_provider<double, double>::add<fn_add_handler>("operator+");
Который затем будет принимать аргументы в начале и принимать их как аргументы шаблона для второго типа (так что не нужно будет набирать <int, int>
часть дважды).
Просто для уточнения; Я знаю о том, что я бы my_add_handler
шаблон как это:
handler_provider<int, int>::add<my_add_handler<int,int>>("test");
Но именно это дублирование я хочу опустить ( <int,int>
).
Тем не менее, я продолжаю получать ошибки с последней частью. handler_provider::add
Метод определяется следующим образом (управление именами параметров, как упомянуто выше, опущено, потому что здесь дело не в этом и работает как положено):
template<class... Ts>
struct handler_provider
{
// Overload for templates, such as 'test_handler'
template<template<class...> class F>
static void add(const std::string name)
{
handler_provider<Ts...>::add<F<Ts...>>(name);
}
// Overload for non-template (or already specialized) handlers (aka. function pointers)
template<function_type F>
static void add(const std::string name)
{
fn_handlers[name] = F;
}
};
Первая перегрузка, как уже было сказано, предполагается для точного случая, который я описал выше, обработчик ниже устанавливает не шаблонные функции и функции, которые полностью специализированы.
Однако это дает мне ошибку, говорящую о том, что внутренний шаблон из вызова, такого как показано выше, не может быть выведен. Я не думал, что сказал компилятору вообще что-либо выводить, я завершил специализированные аргументы шаблона в вызове (снова):
handler_provider<int, int>::add<my_add_handler>("operator+");
Аргументы для внешнего шаблона variadic class... Ts
явно названы <int, int>
и простой аргумент для внутреннего шаблона-шаблона называется my_add_handler
, Тем не менее, компилятор, кажется, игнорирует это (?). Это вывод, который я получаю (GCC 5.4.0 с помощью -std=c++14
):
$ g++ -std=c++14 sci_e1.cpp -o sci
sci_e1.cpp: In function ‘int main()’:
sci_e1.cpp:45:55: error: no matching function for call to ‘handler_provider<int, int>::add(const char [5])’
handler_provider<int, int>::add<my_add_handler>("operator+");
^
sci_e1.cpp:17:15: note: candidate: template<template<class ...> class typedef F F> static void handler_provider<Ts>::add(std::__cxx11::string) [with F = F; Ts = {int, int}]
static void add(const std::string name)
^
sci_e1.cpp:17:15: note: template argument deduction/substitution failed:
sci_e1.cpp:24:15: note: candidate: template<void (* F)(int)> static void handler_provider<Ts>::add(std::__cxx11::string) [with void (* F)(int) = F; Ts = {int, int}]
static void add(const std::string name)
^
sci_e1.cpp:24:15: note: template argument deduction/substitution failed:
sci_e1.cpp:45:55: error: could not convert template argument ‘my_add_handler’ to ‘void (*)(int)’
handler_provider<int, int>::add<my_add_handler>("operator+");
^
Я получаю вторую ошибку, это совершенно нормально и не должно быть проблемой, так как эта перегрузка должна быть исключена из разрешения перегрузки для типов шаблонов. Первая ошибка — та, которая сводит меня с ума.
Clang (3.9.0) немного точнее:
$ clang++ -std=c++14 sci_e1.cpp -o sci
sci_e1.cpp:45:3: error: no matching function for call to 'add'
handler_provider<int, int>::add<my_add_handler>("test");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sci_e1.cpp:17:15: note: candidate template ignored: invalid explicitly-specified argument for
template parameter 'F'
static void add(const std::string name)
^
sci_e1.cpp:24:15: note: candidate template ignored: invalid explicitly-specified argument for
template parameter 'F'
static void add(const std::string name)
^
1 error generated.
Но я до сих пор не понимаю, где я здесь не прав. Что мне не хватает?
Спасибо,
Себастьян
Для лучшего тестирования, вот полный пример:
#include <unordered_map>
#include <string>
#include <iostream>
// Handler type
using function_type = void(int);
// Dispatch table
std::unordered_map<std::string, function_type*> fn_handlers;
// Handler provider (to install new handlers)
template<class... Ts>
struct handler_provider
{
// Overload for templates, such as 'test_handler'
template<template<class...> class F>
static void add(const std::string name)
{
handler_provider<Ts...>::add<F<Ts...>>(name);
}
// Overload for non-template (or already specialized) handlers (aka. function pointers)
template<function_type F>
static void add(const std::string name)
{
fn_handlers[name] = F;
}
};template<class A, class B>
void test_handler(int v)
{
// Something here A and B are needed
}
void other_handler(int v)
{
// A handler without specialization
}
int main()
{
// Install handlers
handler_provider<int, int>::add<test_handler>("testii");
handler_provider<double, int>::add<test_handler>("testdi");
handler_provider<bool, bool, int>::add<other_handler>("otherbbi");
// Dispatch
fn_handlers["testii"](5); // Sould call test_handler<int, int>
fn_handlers["testdi"](5); // Should call test_handler<double, int>
fn_handlers["otherbbi"](5); // Should call other_handler
}
Проблема заключается в следующем: в соответствии со стандартом [temp.arg.template] / 1,
[a] аргумент шаблона для параметра шаблона должен быть именем шаблона класса или псевдонимом
шаблон, выраженный в виде id-выражения.
Поэтому вы не можете создать экземпляр шаблона
template<template<class...> class F>
static void add(const std::string name) {
handler_provider<Ts...>::add<F<Ts...>>(name);
}
с шаблоном функции test_handler
,
Чтобы это исправить, нужно сделать test_handler
вместо этого шаблонный функтор, т.е. измените его на
template<class A, class B>
struct test_handler {
void operator()(int v) {
// Something here A and B are needed
std::cout << __PRETTY_FUNCTION__ << " called with v = " << v << std::endl;
}
};
К сожалению, теперь это уже не тип void(*)(int)
так что вы не можете вставить его в unordered_map
, Поэтому вы должны изменить элементы карты на std::function<function_type>
и настроить add
перегрузка для шаблонных функторов в
// Overload for templates, such as 'test_handler'
template<template<class...> class F>
static void add(const std::string name) {
fn_handlers[name] = F<Ts...>{};
}
Полный код теперь выглядит так:
#include <iostream>
#include <functional>
#include <string>
#include <unordered_map>
// Handler typ
using function_type = void(int);
// Dispatch table
std::unordered_map<std::string, std::function<function_type>> fn_handlers;
// Handler provider (to install new handlers)
template<class... Ts>
struct handler_provider {
// Overload for templates, such as 'test_handler'
template<template<class...> class F>
static void add(const std::string name) {
fn_handlers[name] = F<Ts...>{};
}
// Overload for non-template (or already specialized) handlers (aka. function pointers)
template<function_type F>
static void add(const std::string name) {
fn_handlers[name] = F;
}
};
template<class A, class B>
struct test_handler {
void operator()(int v) {
// Something here A and B are needed
std::cout << __PRETTY_FUNCTION__ << " called with v = " << v << std::endl;
}
};
void other_handler(int v) {
// A handler without specialization
std::cout << __PRETTY_FUNCTION__ << " called with v = " << v << std::endl;
}
int main() {
// Install handlers
handler_provider<int, int>::add<test_handler>("testii");
handler_provider<double, int>::add<test_handler>("testdi");
handler_provider<bool, bool, int>::add<other_handler>("otherbbi");
// Dispatch
fn_handlers["testii"](5); // Sould call test_handler<int, int>
fn_handlers["testdi"](5); // Should call test_handler<double, int>
fn_handlers["otherbbi"](5); // Should call other_handler
}
Это именно то, что вы хотите, как можно увидеть в этом coliru.
Если вы не хотите использовать std::function
из-за накладных расходов (на моей платформе std::function
использует 32 байта вместо 8 байтов для указателя) вы также можете просто написать свою собственную структуру стирания типов для обработчиков.
Других решений пока нет …