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
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.
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:
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.
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:
template<class ... Ts> struct X { X(Ts...) };
X x1{1}; // OK X<int>
X x11; // OK X<>
template<class T> X xv{(T*)0}; // OK decltype(xv<int>) == X<int*>
extern X x2; // NOT OK, needs to be a definition
X arr[10]; // OK X<>
X x1{1}, x2{2}; // OK, deduced to the same type X<int>
X *pointer = 0; // NOT OK
X &&reference = X<int>{1};
X function(); // NOT OK
We propose two techniques to support template argument deduction for class templates:
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.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:
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.
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
}
};
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.
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}
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.
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), ...
- a static assertion (Clause 7),
- controlling template instantiation (14.7.2),
- guiding template parameter deduction for constructors (14.9),
- use of attributes (Clause 7), and
- nothing (in the case of an empty-declaration ).
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:Also, change the note at the bottom of §7p1 [dcl.dcl] as followsblock-declaration
nodeclspec-function-declaration
function-definition
template-declaration
deduction-guide
explicit-instantiation
linkage-specification
namespace-definition
empty-declaration
attribute-declaration
block-declaration
[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 formdeclarator-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:
- For each constructor of the class template designated by the template-name, a function template with the following properties is a candidate:
- The template parameters are the template parameters of the class template followed by the template parameters (including default template arguments) of the constructor, if any.
- The types of the function parameters are those of the constructor.
- The return type is the class template specialization designated by the template-name and template arguments corresponding to the template parameters obtained from the class template.
- For each deduction-guide, a function or function template with the following properties is a candidate:
- The template parameters, if any, and function parameters are those of the deduction-guide.
- The return type is the simple-template-id of the deduction-guide.
Modify the first paragraph of clause 14 [temp] as follows
The declaration in a template-declaration shall
- declare or define a function, a class, or a variable, or
- define a member function, a member class, a member enumeration, or a static data member of a class template or of a class nested within a class template, or
- define a member template of a class or class template, or
- be a deduction-guide, or
- be an alias-declaration.
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.