Search code examples
c++templatesstd-functionpartial-specialization

Why is std::function implemented as a partial template specialisation with an empty base declaration?


Consider the declarations for std::function:

template< class >
class function; /* undefined */

template< class R, class... Args >
class function<R(Args...)>;

[source: cppreference.com]

We have a non-specialised declaration of the template that declares an empty structure, followed by a (partial?) specialisation, but unlike partial specialisations that I have normally seen, this one only uses types declared in its template arguments (doesn't hard-code any types). Its purpose appears to be so that when std::function is instantiated like:

std::function<float(int, int)>

Then R gets the return type (float) and (int, int) goes into the parameter pack Args....

When implementing my own memoisation class as an exercise, I found I had to provide an identical template interface as std::function's to get my memoisation class to work, as it wraps a std::function object but I likewise need access to the return type and arguments so I can forward to the wrapped function using the function-call operator:

template <class>
class memoiser;

template <class R, class... Args>
class memoiser<R(Args...)> {
    memoiser(std::function<R(Args...)>);
    R operator()(Args... args);
}

I noticed that attempting to remove the first declaration causes the second to not compile, and without it being done this way, the only alternative is:

template <class R, class... Args>
class memoiser { ... }

But that's not right as then, memoiser has to be instantiated as memoiser<float, int, int> rather than memoiser<float(int, int)>, this is unidiomatic and doesn't work when a free function is passed in via decltype (whereas the working version does).

But what exactly is going on here? I know the second partial specialisation that works seems to be required to correctly map a function type to the template parameters, but what exact feature of the language is being used to facilitate this? Is it possible to construct other custom exact mappings of more complicated constructs cleanly into template arguments in this way?:

Why is the empty non-specialised template declaration required?

template <class>
class triple;

template <class X, class Y, class Z>
class triple<std::tuple<X, Y, Z>> {}; // can use this to extract X,Y,Z from the tuple instantiated with

...

triple<std::tuple<int, float, char>> butternuts = {};

Solution

  • As far as I can see, this is to support the fancy or idiomatic syntax std::function<float(int, int)>,

    and effectively prevent the other possible syntax std::function<float, int, int> (because I think you can't specialize something from std::).

    std::function is probably based on Boost.Function, and at the time, some compilers were confused by the fancy syntax. So Boost.Function provides both options and calls the second "Portable" syntax.

    Preferred syntax: boost::function<float (int x, int y)> f;

    Portable syntax: boost::function2<float, int, int> f;

    See here: https://www.boost.org/doc/libs/1_65_1/doc/html/function/tutorial.html#idp241211680

    Besides being more idiomatic, it could be that at the time, it was also even easier to implement it this way when your only option was to manually program all the cases with different numbers of input parameters, and there were no variadic templates.

    As for what feature of the language is being used, I would say 1) template specialization and 2) functions have well-defined types in the language. float(int, int) is a concrete type, probably one that you cannot instantiate, but in the same way that float(*)(int, int) is also a concrete type, a concrete pointer-to-function type or that float(&)(int, int) is a reference-to function type.