Document number: P0091R4
Revision of P0091R3
Date: 2017-02-06
Reply-To:
Mike Spertus, Symantec (mike_spertus@symantec.com)
Faisal Vali (faisalv@yahoo.com)
Richard Smith (richard@metafoo.co.uk)
Audience: Evolution Working Group
struct A {
A(const A &) = delete;
};
template<typename T>
int f(T t) { return 3; }
template<>
int f<double>(double) = delete;
It seems very odd that ordinary template functions can be deleted but deduction guides cannot. Indeed, we contend that
supporting = delete for deduction guides not only increases consistency but has worthwhile use cases as well.
Consider the following example from “P0433R1: Toward a resolution of US7 and US14: Integrating template
deduction for class templates into the standard library.”
int iarr[] = {1, 2, 3};
int *ip = iarr;
valarray va(ip, 3); // Deduces valarray<int>
The point is that the valarray<T>::valarray(const T *, size_t) constructor is a better match
than the valarray<T>::valarray(const T &, size_t) constructor, so valarray<int> is
preferred over valarray<int *>. Given the typical usage of valarray, we believe that
this is a reasonable default, and that is what we recommend in P0433R1.
However, suppose we had wanted this deduction to be ambiguous for valarray or a similar class. The natural
way to do this would be to delete the implicit deduction guide that takes a pointer.
template<T> valarray(const T *, size_t) -> valarray<T> = delete;
A function definition of the form:Modify §14.9 [temp.deduct.guide] as follows.attribute-specifier-seqopt decl-specifier-seqopt declarator virt-specifier-seqopt = delete ;is called a deleted definition. A deduction-guide (14.9) with a deleted body is also a deleted definition
deduction-guide:Add the following sentence to the end of 14.9p3:
explicitopt template-name ( parameter-declaration-clause ) -> simple-template-id function-bodyopt ;
A deduction-guide shall be declared in the same scope as the corresponding class template.If the function-body is not = delete or = default, the program is ill-formed.
optional o(3); // Deduces optional<int>
optional o2 = o; // Deduces optional<int>
While this seems natural for user code, it is awkward for writers of template libraries
template<typename T> void foo(T t)
{
optional o = t; // T=optional<int> => o is optional<int>, which may not be desired in generic code
optional<T> o2 = t; // o2 is optional<optional<int>>
}
While library writers can get around this by using explicit arguments as above, we could consider the possible extension
that constructing with a single-element braced initializer would be defined to make the deduction guides implicitly generated
from the copy and move constructors the least viable overload.
viable constructors.
template<typename T> void foo(T t)
{
optional o = {t}; // o is now always optional<T>
}
There are a number of benefits to this approach. It would make it easier for library writers to write generic code without worrying whether
they will invoke a copy constructor. Furthermore, “optional o = {t};” is not a natural
way to invoke a copy constructor anyway. Even better, since template constructor deduction is new, there are no backwards-compatibility
issues to worry about.
The main concern is that this rule does not overlap with natural code for invoking
a copy constructor. While we believe the = {t}; approach avoids excessive overlap. If
direct initialization is needed, code such asoptional o{t}; // Want to leave as copy constructor
is not appealing as it is commonly used for
copy construction (also, cf. CWG 1467).
However, the following syntactically correct but less traditionally natural code can help to avoid the overlap.
optional o({t}); // Seems OK to prefer optional<T>::optional(T const &) constructor
The other main objection to copy/move constructor suppression is that it is admittedly a hack to get around a
problem that could also be avoided by using the C++14 approach of explicitly specifying optional<T>.
Still, worthwhile template programming notations are sometimes considerably more convoluted than this. so we will leave to EWG
to decide.