Partial Template Specialization for template<auto>

Recently I've been working on a small library that utilizes C++17's P0127R1 Declaring non-type template arguments with auto, or so-called template<auto>. The template would take any non-type compile-time constant argument (but of course with restrictions of non-type template arguments), for instance:

template<auto> class Foo;

Foo<1>               //ok
Foo<true>            //ok
Foo<&MyClass::func>  //ok with pointer to class member function
Foo<int>             //error, expects a non-type argument

Basically, I want to pass a function as a template argument to the class. The function can be a static pointer in form of ReturnType (*) (ArgType ...), a static lvalue-reference in form of ReturnType (&) (ArgType ...), or a pointer-to-member ReturnType (ClassType::*) (ArgType ...). Now the problem is how to handle each case separately, without creating a runtime wrapper (e.g. std::function) of the function?

template<auto F> class Listener
{
	//remember, F is the VALUE of function pointer, reference or PMF, not the TYPE

	//how to get the function signature from F?
	//we can use a helper trait that specializes on decltype(F)
	using FuncSignature = typename FunctionTypeTraitsHelper<decltype(F)>::type;

	//we can get the class type if F is a PMF
	//but this won't compile if F is not PMF, 
	//and if we use a special type for the non-existent type it fails to compile elsewhere
	using ClassType = typename FunctionTypeTraitsHelper<decltype(F)>::class_type;
};

We need type branching! So, can we specialize for non-type template parameters?
The answer is yes, and I believe it is easier to show that by example:

//main template
template<int x> struct Foo;

//specialization for x = 0
template<>
struct Foo<0> {};

//specialization for x = 999
template<>
struct Foo<999> {};

However, this doesn't make sense in our case because each function pointer is potentially a new value, and there is no way to know the value upfront. What we actually want here is partially specializing the class for function pointer vs member function pointer (and potentially others, but for the sake of simplicity let us only focus on these 2).

It would be nice to have specialization like that of template type parameters, like

//base template
template<typename T> struct Foo;

//specialization for PMF
template<class C, typename ReturnType, typename ... ArgType> 
struct Foo<ReturnType (C::*)(ArgType...)>
{
	//we can use C, ReturnType, ArgType... in this scope
};

Except this wouldn't work in case of non-type:

//base template
template<auto F> struct Foo;

//ERROR
template<class C, typename ReturnType, typename ... ArgType> 
struct Foo<ReturnType (C::*)(ArgType...)>;

So we have to think about something else. Firstly, it's possible to partially specialize the template to values of certain type, like this:

//base template
template<auto F> struct Foo;

template<int x>
struct Foo<x>
{
	//in this scope, x is always an int
};

Foo<nullptr> //base template
Foo<42> //specialization

~~
Then, it is possible to have default values for template parameter, and that default value can use other previously defined template arguments. This is often used for extracting or synthesising a type that is used in the context later on:

template<typename T, typename Iter = typename T::iterator_type>
void myfunc(T& container, Iter start, Iter end); // some utility algorithm function

Now combining both techniques, we get this beautiful partial-specialization for PMF:

//base template
template<auto F> struct Listener;

//specialization for PMF
template<
	class C, 
	typename ReturnType,
	typename ... ArgType, 
	typename FunctionType = ReturnType (C::*)(ArgType...),
	FunctionType F
> 
struct Listener<F>
{
	//we can use C, ReturnType, ArgType... AND also F in this scope
};

~~

My apologies for not testing my code through a compiler :D
Pointed out by tcanens, what I've been using in the code is not a default template argument but the synthesized type from C, ReturnType and ArgType....

Now it's possible to construct this partial template specialization as

template<class C, typename ReturnType, typename ... ArgType>
using PMF = ReturnType (C::*)(ArgType...);

//base template
template<auto F> struct Listener;

//specialization for PMF
template<  
    class C, 
    typename ReturnType,
    typename ... ArgType, 
    PMF<C, ReturnType, ArgType...> F //or the uglier version, ReturnType(C::* F)(ArgType...)
> 
struct Listener<F>  
{
    //we can use C, ReturnType, ArgType... AND also F in this scope
};

That's it! The full code which has much more details can be found in ImpossiblyFastEventCPP17.

Partial Template Specialization for template<auto>
Share this