N3601
Revision of a section of N3405=12-0095
2013-03-17
Mike Spertus, Symantec
[email protected]
Daveed Vandevoorde, Edison Design Group
[email protected]

Implicit template parameters

The purpose of this example is to eliminate the need for the redundant template<typename T, T t> idiom. This idiom is widely used, with over 100k hits on Google.

The goal is to be able to replace a template declaration like template<typename T, T t> struct C; with another declaration so that we can instantatiate the template like C<&X::f> instead of having to say C<decltype(&X::f), &X::f>.

The basic idea is to be able to say template<using typename T, T t> struct C {/* ... */}; to indicate that T should be deduced. To describe in more detail, we consider some extended examples of template classes and functions.

Implicit template parameters for classes

Consider a hypothetical compile-time reflection type trait describe_field that can give properties of a class member function.

struct A { void f(int i); double g(size_t s); int i; }; /* ... */ cout << describe_field</* Something indicating we want a description of &A::f */>::name; // Prints "f" cout << describe_field</* Something indicating we want a description of &A::g */>::arity; // prints 1

The question is "what should the declaration of describe_field look like?" Since describe_field takes a non-type template parameter, we would probably use the familiar (100k hits on Google) “template<class T, T t>” idiom template<typename T, T t> struct describe_field { /* ... */ }; /* ... */ cout << describe_field<decltype(&A::f), &A::f>::name; // Prints "f" cout << describe_field<decltype(&A::g), &A::g>::arity; // prints 1 That is pretty ugly, and uglier examples are easier to construct.

The key idea is that passing the type of the second template parameter is redundant information because it can be inferred using ordinary type deduction from the second type parameter. With that in mind, we propose that prefacing a template parameter with using indicates that it should not be passed explicitly as a template argument but instead will be deduced from subsequent non-type template arguments. This immediately allows us to improve the usability of describe_field as follows. template<using typename T, T t> struct describe_field { /* ... */ }; /* ... */ cout << describe_field<&A::f>::name; // OK. T is void(A::*)(int) cout << describe_field<&A::g>::arity; // OK. T is double(A::*)(size_t)

Implicit template parameters for functions

Note that this proposal works identically for functions as well. template<using typename T, T t> T f() { /* ... */ }; int i = f<7>() // OK, T is int

As a motivating example for functions, we present a corrected version of an example from N3405 that now makes it clear that we need to use implicit template parameters. Code like the following is all too familiar: log << "Getting ready to call Order::execute on " << *this << with arguments " << a1 << ", " << a2 << endl; auto result = execute(a1, a2); log << "Order::execute returned " << result << endl;

Here we define a wrapper function that can log calls to general methods at a callsite. (Note that this also leverages the putative describe_field template from above) // Convenience function to variadically print to a stream template<typename T, typename... Ts> void printArgs(ostream &os, T&& t, Ts&&... ts) { os << t << ", "; printArgs(os, forward<Ts>(ts)...); }; void printArgs(ostream &os) {} template<using typename Ret; using typename Cls, using typename... ArgTypes, Ret(*c)(ArgTypes...)> typename result_of<C(ArgTypes...)>::type call_method_and_log ()(Cls *t, ArgsTypes&&... args) { log << "Getting ready to call " << describe_field<m>::name << " with arguments "; printArgs(log, args...); Result result = t->*c(forward<Args>(args)...); log << describe_field<c>::name << " returned " << result; return result; } Now that's out of the way, we can replace the above example by: auto result = call_method_and_log<&A::f>(this, a1, a2);

This is not only simpler than the above, but it is less vulnerable to error as in the original code, the caller would be likely to cut and paste the “Getting ready to call ...” boilerplate and possibly getting the wrong name for the method. Using the “template<typename T, T t>” is very problematic as it is not apparent how (or if) one can specify the signature: template<typename T, T t> ??? call_method_and_log(???);

Note:
In n3405, the call was incorrectly written like call_method_and_log(&A::f, this, a1, a2), which made it appear that the same behavior could be accomplished without implicit parameter lists. However, that signature does not work because then we could not of course have referred to describe_field<c> within call_method_and_log.

Specialization

In addition to simplifying the use of describe_field, implicit template parameters can also be used to simplify its implementation, which we use here to illustrate some additional features of implicit template parameters. template<using typename Ret, using class Cls, using typename... ArgTypes, Ret(Cls::*t)(ArgTypes...)> struct describe_field { static char const *name = /* Some compiler intrinsic */; static size_t const arity = sizeof...(ArgTypes...); /* ... */ }; /* ... */ cout << describe_field<&A::f>::name; cout << describe_field<&A::g>::arity; Of course, we might want to use describe_field to describe fields that are not member functions. Because the parameters introduced with using do not appear in the argument list, we can partially specialize on non-type parameters just like we are used to doing with type parameters: template<using typename T, T t> struct describe_field; template<typename Ret, class Cls, typename... ArgTypes, Ret(Cls::*t)(ArgTypes...)> // Specialize on member function struct describe_field<t> { /* As above */ }; template<typename T, class Cls, Ret Cls::*t> // Specialize on data member struct describe_field<t> { static char const *name = /* Some compiler intrinsic */; typedef T field_type; }; /* ... */ cout << describe_field<&A::f>::name; // OK. Matches partial specialization for member function cout << describe_field<&A::g>::arity; // OK. Matches partial specialization for member function cout << describe_field<&A::i>::name; // OK. Matches partial specialization for data member

The deduction process

Given a template definition of the form: template<using parameter-declaration_1, ..., using parameter-declaration_n, parameter-declaration-list> declaration; that declares a given identifier, we want to unambiguously instantiate identifier<template-argument-list>, where template-argument-list are valid arguments for template-parameter-list.

Let non-type-parameter-list be the the types of the parameter-declaration-list that are non-type template parameters, and type-parameter-list be the remaining parameters. Let non-type-argument-list and type-argument-list denote the corresponding arguments.

Now declare a function template<type-parameter-list, parameter-declaration_1, ..., parameter-declaration_n> void f(non-type-parameter-list); If the call f<type-argument-list>(non-type-argument-list) deduces all of the parameter-declaration_* to a corresponding argument_*, then the original template's instantiation exists and is the result of substituting argument_* for parameter-declaration_* and template-argument-list for template-parameter-list in the original template definition.

Let us illustrate with an example. Suppose we have the following declarations: template<int i> struct X { int m() { return i; } double n(double d) { return i + sqrt(d); } }; template< using int i, using typename... Args, typename R, R (X<i>::*mp)(Args...)> Res func(Args... args) { return X<i>(args...); } /* ... */ cout << func<double, &X<3>::n>(16.0)

Now declare the function f as follows: template<typename R, int i, typename... Args>f(R (X<i>::*mp)(Args...)); Since the call f<double>(&X<3>::n); deduces i==3 and Args... is double (try it!), we conclude func<double, &X<3>::n>(16.0) is 7.0.