N3405=12-0095
2012-09-22
Mike Spertus, Symantec
mike_spertus@symantec.com
Subsumes N2332=07-0192

Template Tidbits

This paper collects several tweaks to C++' template mechanism.

T for two

The motivating example is a putative reflection type trait giving properties of a class member.

struct A { void f(int i); double g(size_t s); }; /* ... */ cout << describe<&A::f>::name; // Prints "f" cout << describe<&A::g>::arity; // prints 1 The question is "what should the declaration of describe look like?" Since it takes a non-type template parameter, we need to specify the type of the parameter using the familiar (100k hits on Google) “template<class T, T t>” idiom template<typename T, T t> struct describe;

Of course, we would then need to change are original code to call describe with the uglier cout << describe<decltype(&A::f), &A::f>::name; // Prints "f" cout << describe<decltype(&A::g), &A::g>::arity; // prints 1 Significantly uglier examples can be constructed with variadics.

Our key idea is that passing the type of the second template parameter is (nearly always) redundant information because it can be inferred using ordinary type deduction from the second type parameter. With that in mind, we propose allowing describe to be declared as follows. template<typename T t> struct describe; /* ... */ cout << describe<&A::f>::name; // OK. T is void(A::*)(int) cout << describe<&A::g>::arity; // OK. T is double(A::*)(size_t) Handling variadics works as follows: // Takes arbitrary list of non-type parameters template<typename... T... t> struct A {/* ... */}; A<7, "foo"> a; // T is the parameter pack <int, const char[4]> Note that this proposal works for functions as well. template<typename T t> T f(); int i = f<7>() // OK, T is int

Extended version

As a more detailed example, we would like to have the same ability to deduce template parameters from values as we do with partial specialization of classes. This allows us to do provide useful and uniform facilities like writing a wrapper function for a given function that has the same signature as the function it wraps, with no runtime overhead.

As a motivating example, I've found code like the following to be all too familiar in projects I have been involved in. 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 describes 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) {} // Write the wrapper once and for all template<typename Result(class T::*m)(typename... Args)> Result wrap_and_log ()(T *t, Args&&... args) { log << "Getting ready to call " << describe<m>::name << " with arguments "; printArgs(log, args...); Result result = t->*m(forward<Args>(args)...); log << describe<m>::name << " returned " << result; return result; } /* ... */ // Now that's out of the way, we can replace the above example by (less than) one line auto result = wrap_and_log(&A::f, this, a1, a2);  

Template argument deduction for constructors

This is a revival of N2332=07-0192, which was deferred to the current standards cycle.

According to the standard, template argument deduction may be done for functions. However, many important C++ idioms, such as for_each loops and factory patterns, use class constructors as if they were functions. Unfortunately, template argument deduction is not done in that case. This makes such constructs substantially more difficult than they need to be, and also makes functors less function-like than they need to be. Furthermore, it is weirdly inconsistent for constructors to participate in overload resolution but not in template argument deduction.

This is often handled by the awkward make_* idiom for constructing template classes. For example, whereas new X(/* ... */) suffices to create non-template classes, I often need to say something like make_tuple(7, bind(std::multiplies<int>, _1, _1)).  Does this extra delegation to the make_tuple function serve any necessary purpose? Well, I can always create a (simple) tuple like new tuple<int, double>(5, 3.2), but the template arguments are redundant because they are implied by the arguments (this is why make_tuple exists!). Really, it would be much better if you could just say new tuple(5, 3.2), which is what we are proposing.

Another problem with the make_* idiom is that it can't be used in all of the ways constructors are used, so code like the following is awkward. using namespace std; template<class Func> class Foo() { public: Foo(Func f) : func(f) {} void operator()(int i) { os << "Calling with " << i << endl; f(i); } private: Func func; }; void bar(vector<int> vi) { int a; for_each(vi.begin(), vi.end(), Foo<???>([&](int i) { a += i*i; })); } If we allowed the compiler to deduce the template arguments, we could simple write the for_each loop asfor_each(vi.begin(), vi.end(), Foo([&](int i) { a += i*i; }

What are the limits on this deduction? All of the classes non-defaulted template parameters need to be directly included in the function arguments. This is not so bad, because template parameter deduction for ordinary functions has the exact same limitation, so we are nothing if not consistent. Neither template<class T> struct F { F(typename T::x); }; auto a = F(x);// Error: Cannot deduce T nor template<class T> T F(typename T::x); auto a = F(x);// Error: Cannot deduce T compile.

Parameter pack scope

This one is truly a tidbit. We propose that the following be well-formed. template<typename... T, void (*)(T...), T... t> struct S { /* ... */}; void f(int, double); S<int, double, f, 5, 4.1> s; Currently, this doesn't work because all template parameters are absorbed by T, even though they are not types. This proposal is simply that a non-type passed as a template parameter terminates the scope of a typename.... Note that examples of this sort come up when trying to pass a function as a template parameter (Although the T for two tidbit helps here also, neither moots the other). Likewise, it is not unreasonable to desire passing some types as parameters and then passing non-type template parameters of the given types.

Virtual (fully-specialized) template methods

“Everybody knows” that template methods cannot be virtual because vtable entries need to point at real methods, not templates. However, a full specialization of a template method is an actual method in itself, and could conceivably be permitted to be virtual. This is interesting, but why would anyone care? Well, in Andrei Alexandrescu's seminal book Modern C++ Design, section 3.1 on"The need for typelists" discusses a scenario (the Factory pattern) and lists two main obstacles (emphasis mine).
However, we cannot fulfill these needs. First, the typedef for WidgetFactory above is not possible because templates cannot have a variable number of parameters. Second, the template syntax Create<Xxx<() is not legal because virtual functions cannot be templates.
He then uses some very ugly (but necessary) techniques to simulate virtual fully-specialized template functions to create the factory with code that (leveraging variadics) is more or less equivalent to the following (see Chapter 9 for explanation of these techniques) template<typename T> struct Type2Type {}; template<typename... Ts> struct AbstractFactoryHelper; template<> struct AbstractFactoryHelper<> {}; template<typename T, typename... Ts> struct AbstractFactoryHelper<T, Ts...> : public AbstractFactoryHelper<Ts...> { virtual T *doCreate(Type2Type<T>) = 0; }; template<typename... Ts> struct AbstractFactory : public AbstractFactoryHelper<Ts...> { template<typename T> T *create() { return doCreate(Type2Type<T>()); } } If we allowed fully-specialized methods to be virtual, not only is the code shorter, it is clearer and more natural with fewer guru techniques (we do not want to pursue conciseness for its own sake!). template<typename... Ts>struct AbstractFactoryHelper; template<> struct AbstractFactoryHelper<> {}; template<typename T, typename... Ts> struct AbstractFactoryHelper<T, Ts...> : public AbstractFactoryHelper<Ts...> { template<> virtual T *create<T>() = 0; }; template<typename... Ts> struct AbstractFactory : public AbstractFactoryHelper<Ts...> { template<typename T> T *create(); } One requirement is likely to be that the specializations are visible in all translation units, but that is satisfied in the motivating examples and can be viewed as a (possibly extended) ODR requirement. The warning in §14.7.3p8 of the standard saying
When writing a specialization, be careful about its location; or to make it compile will be such a trial as to kindle its self-immolation.
still applies, but we've already accepted the consequences of having a language with specializations, let's maximize their benefit.