Search code examples
c++templatesvariadic-templatesexplicit-specialization

template-template-parameter deducation failed for template method (although explicitly-specialized)


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
}

Solution

  • 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.