N3602
Revision of a section of N3405=12-0095
2013-03-14
Mike Spertus, Symantec
mike_spertus@symantec.com
Daveed Vandevoorde, Edison Design Group
daveed@edg.com

Template parameter deduction for constructors

This paper proposes extending template parameter deduction for functions to constructors of template classes. The clearest way to describe the problem and solution is with some examples.

Suppose we have defined the following. vector<int> vi1 = { 0, 1, 1, 2, 3, 5, 8 }; vector<int> vi2; 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; }; Currently, if we want to instantiate template classes, we need to either specify the template parameters or use a "make_*" wrapper, leverage template parameter deduction for functions, or punt completely: pair<int, double> p(2, 4.5); auto t = make_tuple(4, 3, 2.5); copy_n(vi1, 3, back_inserter(vi2)); // Virtually impossible to pass a lambda to a template class' constructor for_each(vi.begin(), vi.end(), Foo<???>([&](int i) { ...})); There are several problems with the above code:

If we allowed the compiler to deduce the template parameters for constructors of template classes, we could replace the above with: pair p(2, 4.5); tuple t(4, 3, 2.5); copy_n(vi1, 3, back_insert_iterator(vi2)); for_each(vi.begin(), vi.end(), Foo([&](int i) { ...})); // Now easy to do We believe this is more consistent and simpler for both users and writers of template classes, especially if we were using user-defined classes that might not have carefully designed and documented make functions like pair, tuple, and back_insert_iterator.

The Deduction Process

We propose to allow a template name referring to a class template as a simple-type-specifier in two contexts:

In the case of a function-notation type conversion (e.g., "tuple(1, 2.0, false)") or a direct parenthesized or braced initialization, the initialization is resolved as follows. First, constructors and constructor templates declared in the named template are enumerated. Let Ci be such a constructor or constructor template; together they form an overload set. A parallel overload set F of function templates is then created as follows: For each Ci a function template is constructed with template parameters that include both those of the named class template and if Ci is a constructor template, those of that template (default arguments are included too) -- the function parameters are the constructor parameters, and the return type is void Deduction and overload resolution is then performed for a synthesized call to F with the parenthesized or braced expressions used as arguments. If that call doesn't yield a "best viable function", the program is ill-formed. Otherwise, the template name is treated as the class-name that is obtained from the named class template with the deduced arguments corresponding to that template's parameters.

Let's look at an example: template<typename T> struct S { template<typename U> struct N { N(T); N(T, U); template<typename V> N(V, U); }; }; S<int>::N x{2.0, 1}; In this example, "S<int>::N" in the declaration of x is missing template arguments, so the approach above kicks in. Template arguments can only be left out this way from the "type" of the declaration, but not from any name qualifiers used in naming the template; i.e., we couldn't replace "S<int>::N" by just "S::N" using some sort of additional level of deduction. To deduce the initialized type, the compiler now creates an overload set as follows: template<typename U> void F(S<int>::N<U> const&); template<typename U> void F(S<int>::N<U> &&); template<typename U> void F(int); template<typename U> void F(int, U); template<typename U, typename V> void F(V, U); (The first two candidates correspond to the implicitly-declared copy and move contructors. Note that template parameter T is already known to be int and is not a template parameter in the synthesized overload set.) Then the compiler performs overload resolution for a call "F(2.0, 1)" which in this case finds a unique best candidate in the last synthesized function with U = int and V = double. The initialization is therefore treated as "S<int>::N<int> x{2.0, 1};"

Note that after the deduction process described above the initialization may still end up being ill-formed. For example, a selected constructor might be inaccessible or deleted, or the selected template instance might have been specialized or partially specialized in such a way that the candidate constructors will not match the initializer.

The case of a simple-declaration with copy-initialization syntax is treated similarly to the approach described above, except that explicit constructors and constructor templates are ignored, and the initializer expression is used as the single call argument during the deduction process.