вывод шаблона-шаблона-параметра не удался для метода-шаблона (хотя он явно специализирован)

Я пытаюсь написать небольшой интерпретатор сценариев, который можно расширять с помощью 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
}

2

Решение

Проблема заключается в следующем: в соответствии со стандартом [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 байтов для указателя) вы также можете просто написать свою собственную структуру стирания типов для обработчиков.

2

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector