N3601
Revision of a section of N3405=12-0095
2013-03-17
Mike Spertus, Symantec
mike_spertus@symantec.com
Daveed Vandevoorde, Edison Design Group
daveed@edg.com
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.
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.
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)
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;
// 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(???);
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
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.