Document number: P0091R3
Revision of P0091R2
Date: 2016-06-24
Reply-To:
   Mike Spertus, Symantec (mike_spertus@symantec.com)
   Faisal Vali (faisalv@yahoo.com)
   Richard Smith (richard@metafoo.co.uk)
Audience: Core Working Group

Template argument deduction for class templates (Rev. 6)

Changes from P0091R2

Summary

This paper proposes extending template argument deduction for functions to constructors of template classes and incorporates feedback from the EWG review of P0091R0 and from implementation experience.

Currently, if we want to construct template classes, we need to specify the template arguments. For example, N4498 on Variadic Lock Guards gives an example of acquiring a lock_guard on two mutexes inside an operator= to properly lock both the source and destination of the assignment. Expanding typedefs, the locks are acquired by the following statement (See the paper for details and rationale). std::lock_guard<std::shared_timed_mutex, std::shared_lock<std::shared_timed_mutex>> lck(mut_, r1); Having to specify the template arguments adds nothing but complexity! If constructors could deduce their template arguments "like we expect from other functions and methods," then the following vastly simpler and more intuitive code could have been used instead. auto lock = std::lock_guard(mut_, r1);

The sections below first spell out the problem in more detail and then makes precise what "like we expect from other functions and methods" means in this context.

The problem

To simplify the examples below, suppose the following definitions are in place. vector<int> vi1 = { 0, 1, 1, 2, 3, 5, 8 }; vector<int> vi2; std::mutex m; unique_lock<std::mutex> ul(m, std::defer_lock); 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; mutex mtx; };

In the current standard, the following objects would be constructed as shown

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 without declaring the lambda for_each(vi2.begin(), vi2.end(), Foo<???>([&](int i) { ...})); lock_guard<std::mutex> lck(foo.mtx); lock_guard<std::mutex, std::unique_lock<std::mutex>> lck2(foo.mtx, ul); // Notation from N4470 auto hasher = [](X const & x) -> size_t { /* ... */ }; There are several problems with the above code: If we allowed the compiler to deduce the template arguments 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 instead of virtually impossible auto lck = lock_guard(foo.mtx); lock_guard lck2(foo.mtx, ul); We believe this is more consistent and simpler for both users and writers of template classes, especially for user-defined classes that might not have carefully designed and documented make functions like pair, tuple, and back_insert_iterator.

The Solution

We propose to allow a template name referring to a class template as a simple-type-specifier or with partially supplied explicit template arguments in two contexts:

We propose two techniques to support template argument deduction for class templates:

These techniques can work well together as will be explained below. They can also be adopted separately. Both forms received consensus straw polls from EWG (although note that the authors did not vote unanimously in support of implicit guides).

Implicitly synthesized Deduction Guides (from existing constructors)

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 (i.e. the implicitly synthesized deduction guides) 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 the template-name followed by the template-parameters of the class template enclosed in <>

Deduction and overload resolution is then performed for an invented 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 return type of the selected F template specialization becomes the deduced class template specialization.

Let's look at an example: template<typename T> struct UniquePtr { UniquePtr(T* t); ... }; UniquePtr dp{new auto(2.0)};

In the above example, UniquePtr is missing template arguments in the declaration of 'dp' so they have to be deduced. To deduce the initialized type, the compiler then creates an overload set as follows:

template<typename T> UniquePtr<T> F(UniquePtr<T> const&); template<typename T> UniquePtr<T> F(UniquePtr<T> &&); template<typename T> UniquePtr<T> F(T *p);

Then the compiler performs overload resolution for a call "F(2.0)" which in this case finds a unique best candidate in the last synthesized function and after final substitution, deduces the class template specialization as UniquePtr<double>

Let's look at a more involved 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> S<int>::N<U> F(S<int>::N<U> const&); template<typename U> S<int>::N<U> F(S<int>::N<U> &&); template<typename U> S<int>::N<U> F(int); template<typename U> S<int>::N<U> F(int, U); template<typename U, typename V> S<int>::N<U> 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 and deduced type of S<int>::N<int>. 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.

Explicitly specified Deduction Guides

While the above procedure generates many useful deducible constructors, some constructors that we would like to be deducible are not. For example, one could imaging a function make_vector defined as follows:

template<typename Iter> vector<Iter::value_type> make_vec(Iter b, Iter e) { return vector<Iter::value_type>(b, e); }

Although there is no constructor in vector from which we can deduce the type of the vector from two iterators, one would like to be able to deduce the type of the vector from the value type of the two iterators. For example, some implementations of the STL define their value_type typedef as follows template<typename T, typename Alloc=std::allocator<T>> struct vector { struct iterator { typedef T value_type; /* ... */ }; typedef iterator::value_type value_type; /* ... */ }; The detour through vector<T>::iterator keeps us from deducing that T is char in a constructor call like vector(5, 'c'). We would certainly like constructors like that to work.

We suggest a notation to allow explicit specification of a deduction guide in the same semantic scope as the class template using the following syntax: template<typename T, typename Alloc = std::allocator<T>> struct vector { /* ... */ }; template<typename Iter> vector(Iter b, Iter e) -> vector<typename iterator_traits<Iter>::value_type>

In effect, this allows users to leverage all the deduction rules that are specifiable by any function with a standard first-class name and no boilerplate code in the body. It also allows us to suppress a standard deduction from the above process via "= delete;"

Note that a deduction guide is not a function and shall not have a body. It participates in deduction of class template arguments in a similar way to the synthesized deduction guides.

Additionally, it is worthwhile to note the following:

Alternate notation

As an alternative to the above notation for explicit deduction guides, one could consider a “declaration notation”. E.g., template<typename Iter> vector<typename iterator_traits<Iter>::value_type> vector(Iter b, Iter e); template<typename Iter> auto vector(Iter b, Iter e) -> vector<typename iterator_traits<Iter>::value_type>;

The point is that this uses a familiar notation to declare what the canonical “make function” would look like as a function declaration. As the above lines are not legal C++14 (cf. §14p5), this would not reinterpret existing function declarations, and the compiler or linker would not need to check for a function definition. This could potentially be easier for programmers to learn as they do not need to learn a new grammatical construct, and there is indeed an “function” instantiated to match the declaration. The downside of the “declaration notation” is of course the quotes around “function”, as this is not actually a function declaration.

The prototype uses “declaration notation”, but we believe there are no technical parsing obstacles to either (always a relief when defining new C++ features!) and that it is a matter of the preference of the committee.

A note on injected class names

The focus on this paper is on simplifying the interface of a class for its clients. Within a class, one may need to explicitly specify the arguments as before due to the injected class name:

template<typename T> struct X { template<typename Iter> X(Iter b, Iter e) { /* ... */ } template<typename Iter> auto foo(Iter b, Iter e) { return X(b, e); // X<U> to avoid breaking change } template<typename Iter> auto bar(Iter b, Iter e) { return X<Iter::value_type>(b, e); // Must specify what we want } };

Code compatibility

While we cannot say whether it is a problem in practice, we should point out a scenario where auto-deduction can break compatibility.

Suppose I produce a library and I'm under license to preserve source compatibility across all 1.x upgrades, and I have this class template in version 1.0:

template struct X { X(T); };

... and in version 1.1 I rewrite it as this:

template struct X { struct iterator { typedef T type; }; X(typename iterator::type); };

If one of my users upgrades to C++17, with this change in the language, I am no longer complying with the terms of my licensing. Likewise, if this language change happens between me releasing 1.0 and 1.1, I can no longer release version 1.1 because it might break some of my existing customers.

The point is: current code does not express any intent about whether class template parameters are deducible, based on whether they use the version 1.0 code or the version 1.1 code. But this change makes that implicit property into part of the de facto interface of the code.

Pros and cons of implicit deduction guides

In light of the above, we think it is worth calling out the benefits and costs of providing implicit deduction guides versus requiring explicit deduction guides everywhere

Basically, having to manually specify boilerplate for what is obviously expected has an insidious cost as any (honest) Java programmer can tell you. There are natural implementations of all of the examples in The Problem section above where only implicit deduction guides are necessary. (Alternate implementations of those classes may require explicit guides but do not create unnatural deductions). As many classes have dozens of constructors, not only is creating myriad explicit deduction guides tedious and error-prone but will (predictably) drift out of sync with the actual constructors as the class evolves. While not suitable for all purposes, this is a much-requested feature to simplify routine programming (cf. range-based for) and current practice or explicit deduction guides remain available (see next paragraph for exceptions) if the implicit deduction is not sufficient, mitigating downside.

So what is the cost of implicit deduction guides? The Code compatibility section above shows that equivalent code in C++14 may no longer be equivalent in C++17 (Note that this example does not change the behavior of C++14 code when compiled with a C++17 compiler). This particular incompatibility can be rectified by adding explicit deduction guides as needed.

Another cost of implicit deduction guides is that they may trigger instantiations that cause hard errors. For example, consider the following class

template<class T> struct X { using ty = T::type; static auto foo() { return typename T::type{} }; X(ty); #1 X(decltype(foo())); #2 X(T); }; template<class T> struct X<T*> { X(...); };

For such a class, the prototype implementation allows

X x{(int *)0};

but normal instantiation rules suggest a hard error. We plan to discuss implications with the committee. Note that the current X<int *> x{0}

remains legal.

Universal reference interactions

By deducing the template arguments, sometimes and rvalue reference can be reinterpreted as a universal reference as the following example due to Sebastian Gesemann shows. template<class T> struct Wrapper { T value; Wrapper(T const& x): value(x) {} Wrapper(T && y): value(std::move(x)) {} }; int main() { std::string foo = "Hello"; auto w = Wrapper(foo); // Error }

In the implicit deduction guide for the second constructor, T && is now interpreted as a universal reference rather than an rvalue reference, resulting in a failure to find a valid match. This does not seem to result in a dangerous deduction but rather a failure to compile, but Core should consider this case. The resolution is to write an explicit deduction guide will need to be written to explain the intent.

template<typename T> Wrapper(T &&y) -> Wrapper<remover_reference_t<T>>;

Of course, traditional constructor invocation without deduction will continue to work as well.

Wording

Modify the beginning of §3.1 [basic.def] as follows

A declaration (Clause 7) may introduce one or more names into a translation unit or redeclare names introduced by previous declarations. If so, the declaration specifies the interpretation and attributes of these names. A declaration may also have effects including:

A declaration is a definition unless it declares a function without specifying the function's body (8.), it contains the extern specifier (7.1.1) or a linkage-specification and neither an initializer nor a function-body, it declares a deduction-guide (14.9), it declares a static data member in a class definition (9.2, 9.2.3), ...

Insert a paragraph after §5.2.3p1 [expr.type.conv] as follows:

A simple-type-specifier (7.1.6.2) or typename-specifier (14.6) followed by a parenthesized expression-list constructs a value of the specified type given the expression list. If the expression list is a single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression (5.4). If the type specified is a class type, the class type shall be complete. If the expression list specifies more than a single value, the type shall be a class with a suitably declared constructor (8.5, 12.1), and the expression T(x1, x2, ...) is equivalent in effect to the declaration T t(x1, x2, ...); for some invented variable t , with the result being the value of t as a prvalue.

A template-name corresponding to a class template followed by a parenthesized expression-list constructs a value of a particular type determined as follows. Given such an expression T(x1, x2, ...), construct the declaration T t(x1, x2, ...); for some invented variable t. Define the deduced type U of T(x1, x2, ...) to be decltype(t), then the expression T(x1, x2, ...) will construct the same value of type U as the expression U(x1, x2, ...).

Change §7p1[dcl.dcl] as follows

declaration:
block-declaration
nodeclspec-function-declaration
function-definition
template-declaration
deduction-guide
explicit-instantiation
linkage-specification
namespace-definition
empty-declaration
attribute-declaration
block-declaration
Also, change the note at the bottom of §7p1 [dcl.dcl] as follows
[Note: asm-definitions are described in 7.4, and linkage-specifications are described in 7.5. Function-definitions are described in 8.4 and template-declarations and deduction-guides are described in Clause 14.

Modify the start of §7.1.6.2 [dcl.type.simple] as follows

The simple type specifiers are

simple-type-specifier:
nested-name-specifieropt type-name
nested-name-specifier template simple-template-id
nested-name-specifieropt template-name
char
char16_t
char32_t
wchar_t
bool
short
int
long
signed
unsigned
float
double
void
auto
decltype-specifier

Modify §7.1.6.2p2 as follows

The simple-type-specifier auto is a placeholder for a type to be deduced (7.1.6.4). A type-specifier of the form typenameopt nested-name-specifieropt template-name is a placeholder for a deduced class type and shall appear only as a decl-specifier in the decl-specifier-seq of a simple-declaration (7.1.6.5) or as the simple-type-specifier a deduced class type and shall appear only as a decl-specifier in the decl-specifier-seq of a simple-declaration (7.1.6.5) or as the simple-type-specifier in an explicit type conversion (functional notation) (5.2.3). The template-name shall name a class template that is not an injected-class-name. The other simple-type-specifiers specify either a previously-declared type, a type determined from an expression, or one of the fundamental types (3.9.1). Table 9 summarizes the valid combinations of simple-type-specifiers and the types they specify.

Modify Table 9 in §7.1.6.2 [dcl.type.simple] as follows

Specifier(s) Type
type-name the type named
simple-template-id the type as defined in 14.2
template-name placeholder for a type to be deduced
char “char”

Add a new section §7.1.6.5

7.1.6.5     Deduced Class Template Types     [deduced.class.type]
If a placeholder for a deduced class type appears as a decl-specifier in the decl-specifier-seq of a simple-declaration, the init-declarator of that declaration shall be of the form
declarator-id attribute-specifier-seqopt initializer.
The placeholder is replaced by the return type of the function selected by overload resolution for class template deduction (13.3.1.8). If the init-declarator-list contains more than one init-declarator, the type that replaces the placeholder shall be the same in each deduction.

[Example:

template<class T> struct container {
    container(T t) {}
    template<class Iter> container(Iter beg, Iter end);
};
template<class Iter>
container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>;
std::vector<double> v = { /* ... */};

container c(7); // OK. Deduces int for T
auto d = container(v.begin(), v.end()); // OK. Deduces double for T
container e{5, 6}; // Ill-formed. int is not an iterator
— end example]
 

Modify the definition of member-declaration near the start of §9.2 [class.mem] as below

member-declaration:
attribute-specifier-seqopt decl-specifier-seqopt member-declarator-listopt ;
function-definition
using-declaration
static_assert-declaration
template-declaration
deduction-guide
alias-declaration
empty-declaration

Add a new section 13.3.1.8 as follows

13.3.1.8     Class Template Deduction     [class.template.deduction]
The overload set consists of:

Modify the first paragraph of clause 14 [temp] as follows

The declaration in a template-declaration shall

Modify §14.1p11 [temp.param] as follows:

If a template-parameter of a class template, variable template, or alias template has a default template-argument, each subsequent template-parameter shall either have a default template-argument supplied or be a template parameter pack. If a template-parameter of a primary class template, primary variable template, or alias template is a template parameter pack, it shall be the last template-parameter. A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list (8.3.5) of the function template or has a default argument (14.8.2). A template parameter of a deduction guide template (14.9) that does not have a default argument shall be deducible from the parameter-type-list of the deduction guide template.

Modify §14.6p3 [temp.res] as follows:

When a qualified-id is intended to refer to a type that is not a member of the current instantiation (14.6.2.1) and its nested-name-specifier refers to a dependent type, it shall be prefixed by the keyword typename, forming a typename-specifier. If the qualified-id in a typename-specifier does not denote a type or a class template, the program is ill-formed.

Modify §14.6p4 [temp.res] as follows:

If a specialization of a template is instantiated for a set of template-arguments such that the qualified-id prefixed by typename does not denote a type or a class template, the specialization is ill-formed. The usual qualified name lookup (3.4.3) is used to find the qualified-id even in the presence of typename.

Modify §14.6p6 [temp.res] as follows:

If, for a given set of template arguments, a specialization of a template is instantiated that refers to a qualified-id that denotes a type or a class template, and the qualified-id refers to a member of an unknown specialization, the qualified-id shall either be prefixed by typename or shall be used in a context in which it implicitly names a type as described above.

Modify §14.6p7 [temp.res] as follows:

Within the definition of a class template or within the definition of a member of a class template following the declarator-id, the keyword typename is not required when referring to the name of a previously declared member of the class template that declares a type or a class template. [Note: such names can be found using unqualified name lookup (3.4.1), class member lookup (3.4.3.1) into the current instantiation (14.6.2.1), or class member access expression lookup (3.4.5) when the type of the object expression is the current instantiation (14.6.2.2). &emdash; end note]

At the end of clause 14 [Template], add a new section

14.9 Deduction guides     [temp.deduction.guide]

Deduction guides are used when a template-name appears as a type specifier for deducing class types (7.1.6.5). Deduction guides are not found by name lookup. Instead, when performing class template deduction (13.3.1.8), any deduction guides declared for the class template are considered.

deduction-guide:
template-name ( parameter-declaration-clause ) -> simple-template-id ;

The same restrictions apply to the parameter-declaration-clause of a deduction guide as in a function declaration (8.3.5). The simple-template-id shall name a class template specialization. The template-name shall be the same identifier as the template-name of the simple-template-id. A deduction-guide shall be declared in the same scope as the corresponding class template.