I am attempting to write a small script interpreter that is extensible via c++. For that purpose function handlers are inserted into a dispatch table. To simplyfy my question, the handlertype is defined as follows (in the real code, this contains parameters for an argument-list and return type):
// Handler type
using function_type = void(int);
The dispatch table, for now, is a simple unordered map with a (kind of) mangled name as key to implement overloading:
// Dispatch table
std::unordered_map<std::string, function_type*> fn_handlers;
Methods are added to this table either direct, e.g. like a simple method that takes two arguments of type int
(operator+ii
is the manged name for this in my case):
fn_handlers["operator+ii"] = my_add_handler;
However, many handlers, especially those related to basic math, do accept a variety of arguments, all combinations of int
and double
would be valid, yielding to 4 methods and 4 dispatch table entries. Therefore I decided to implement those methods using templates.
To give an example, this might be basically this (again simplyfied):
template<class A, class B>
void my_add_handler(int /* simplified... */)
{
// Something here A and B are needed
}
The dispatch table then is filled like this:
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>;
Still a lot to type, but this is okay for now. Anyway since there is obviously a correlation between the template parameters and the method signature (mangled name), I attempted to automate this that you could write (parameter name mangeling would be done inside 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+");
Which then would take the arguments at the beginning and take them as template arguments for the second type (so that would not have to type the <int, int>
part twice).
Just for clarification; I am aware of the I would explicitly specialize the my_add_handler
template like this:
handler_provider<int, int>::add<my_add_handler<int,int>>("test");
But it is exactly this duplication that I want to omit (twict the <int,int>
).
However, I keep getting errors with the last part. The handler_provider::add
method is defined as follows (the parameter name mangeling as mentioned above is left out because it is not the point here and works as expected):
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;
}
};
The first overload, as said, is supposed for the exact case I described above, the handler below installs non-template functions and those which are fully specialized.
However, this gives me an error, telling be that the inner template from a call such as shown above cannot be deduced. I did not think that I told the compiler to deduce anything at all, I complete specialized the template arguments in the call (again):
handler_provider<int, int>::add<my_add_handler>("operator+");
The arguments for the outer variadic template class... Ts
are explicitly named <int, int>
and the simple argument for the inner template-template is named as my_add_handler
. However, the compiler seems to ignore this(?). This is the output I get (gcc 5.4.0 using -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+");
^
I get the second error, thats completly okay and should not be a problem as this overload should be kicked out of overload resolution for template types. The first error is the one that drives me crazy.
Clang (3.9.0) is a little more precise:
$ 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.
But I still do not understand where I am wrong here. What am I missing?
Thanks,
Sebastian
For better testing, here is an full example:
#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
}
The issue is the following: According to the standard, [temp.arg.template]/1,
[a] template-argument for a template template-parameter shall be the name of a class template or an alias template, expressed as id-expression.
Therefore you can't instantiate the template
template<template<class...> class F>
static void add(const std::string name) {
handler_provider<Ts...>::add<F<Ts...>>(name);
}
with the function template test_handler
.
To fix this you have to make test_handler
a templated functor instead, i.e. change it to
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;
}
};
Unfortunately, now this is no longer of type void(*)(int)
so you can't insert it into the unordered_map
. Therefore you have to change the elements in the map to std::function<function_type>
and adjust the add
overload for templated functors to
// Overload for templates, such as 'test_handler'
template<template<class...> class F>
static void add(const std::string name) {
fn_handlers[name] = F<Ts...>{};
}
The full code now looks like this:
#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
}
This does exactly what you want, as can be seen in this coliru.
If you don't want to use std::function
because of the overhead (on my platform std::function
uses 32 bytes instead of 8 bytes for a pointer) you can also just write your own type erasure struct for the handlers.