2

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
}
  • 1
    The specified template parameter to `add()` is a template class. "test_handler" is not a template class. It's a template function. The two are not interchangable. – Sam Varshavchik Oct 15 '16 at 19:34
  • Thanks for the hint! I already thought so as I attempted to use `F` as a function directly from within the add method and got the error "primary expression expected...". I attampted to do something like this: `template – Sebastian Büttner Oct 15 '16 at 19:36
  • Well, try passing the function as a parameter to `add()`, which is otherwise ignored, but parametrized as `template void add(R(Args...) *f)`. Something along those lines. – Sam Varshavchik Oct 15 '16 at 19:43
  • You are completly correct, I've totally overseen this. I'll try! – Sebastian Büttner Oct 15 '16 at 19:47
  • Well that was a little to euphoric, first point is that the handlers are still `void(int)`. All handlers share the same signature in the c++ interface, At the level of the scripting language I will create on top of this, there are different parameters (this is what my name mangeling is for). Anyway as I do not want to double the `` for a single add call, my idea was to omit them (therefore make the passed function template a template-template parameter) and explicitly specialize them in the call to `add` as `F`. – Sebastian Büttner Oct 15 '16 at 19:55
  • Just for clarification; I am aware if I would explicitly specialize the my_add_handler template like this: `handler_provider::add>("test");` But it is exactly this duplication that I want to omit (twict the ``). – Sebastian Büttner Oct 15 '16 at 20:19

1 Answers1

3

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.

Corristo
  • 4,911
  • 1
  • 20
  • 36
  • Thanks for your reply! I thought about this approach already by myself. Unfortunately this is not a real option here, as I do not want to change the way handlers are defined. So I think the valid answere to this would be that it is simply not possible (at least for now). – Sebastian Büttner Oct 23 '16 at 22:52
  • @SebastianBüttner May I ask why you don't want the handler definition to change? – Corristo Oct 24 '16 at 07:23