P1021R0
Mike Spertus, Symantec
mike_spertus@symantec.com
2018-05-07
Audience: Evolution Working Group
We propose allowing deduction guides for function templates. In addition to the (not to be underestimated) value of increased consistency, we provide the following motivating use cases.
std::reference_wrapper is commonly used to effect pass-by-reference to function templates with generic parameter types. Writing a function template that can be used this way is naturally simplified with deduction guides.
template <typename T> void f(T t) { t.foo(); /* ... */}
template <T> f(reference_wrapper<T>) -> f<T &>;
Now f will unwrap reference wrappers arguments.
struct X { void foo(); /* ... */};
int main()
{
X x;
f(ref(x)); // Passes by reference as desired. Ill-formed without deduction guide
}
Note: In C++ 17, something similar may be accomplished by rewriting f as
// Helper for converting reference wrappers to references
template <typename T> struct unwrap { using type = T };
template <typename T> struct unwrap<std::reference_wrapper<T>> { using type = T & }
template <typename T> using unwrap_t = typename unwrap<T>::type;
template <typename T> void f(T t)
{
remove_reference_t<unwrap_t<T>> &actualT{t};
actualT.foo(); /* ... */
}
However, not only is this more cumbersome in our opinion, it requires that the user of f look inside the implementation of f to understand
that it conforms to the “reference_wrapper mini-language”, whereas the proposed version
advertises that in the interface.
For function templates that accept a const argument, it is generally most efficient to pass
small argument types, such as int, by value and large or uncopyable types by const &.
The Boost Call Traits library has a call_traits<T>::param_type traits that gives the
most efficient way to pass, but it is awkward to to use.
// C++17
template <typename T> void do_f(T t); // Implements f
template <typename T> inline void f(T && t)
{
do_f<typename boost::call_traits<decay_t<T>>::type>(t);
}
With deduction guides, this again naturally simplifies to a form that no longer requires examining the body of f
template <typename T> void f(T t); // Implement here. No need to delegate
template <typename T> f(T t) -> f<typename boost::call_traits<T>::type>;
// Assume p0945r0 and std::experimental::invocation_type
template<typename F, typename R, typename ...T>
future<R> call_later(F fn, T ...t);
template<typename F, typename T> struct call_later_helper;
template<typename F, typename R, typename ...T< struct call_later_helper<R(T...)> {
using call_later = ::call_later<F, R, T...>;
};
template<typename F, typename ...T>
call_later(F fn, T &&...t) -> call_later_helper<F, invocation_type_t<F, T&&...>>::call_later;
template <typename T> void f(optional<T>);
template <typename CharT, typename Traits> void g(basic_string_view<CharT, Traits>);
/* ... */
f(7); // Would like to deduce f<int> with arg of optional<int>
g("foo"); // Would like to deduce g<char, char_traits<char>> with arg of string_view
Note that pending a consensus around terse notation for concepts, this paper does not propose allowing declarations of function templates without the template keyword.
void f(optional); // Not part of this proposal
/* ... */
f(7); // Does not deduce f<int>
tuple f() { return {3, 5}; } // tuple<int, int>
All return statements that deduce should deduce the same type, and at least one return statement should be able to deduce a return type.
optional f() { return 7; } // optional<int>
auto g() -> optional { if(noValue()) return {} else return 7; } // optional<int>
optional h() { if(foo()) return "bar" else return 7; } // Ill-formed: Ambiguous
template<typename T, typename U> struct TU { T t; U u; };
TU<int, double> tu{1, 2.3}; // OK
TU tu2{1, 2.3}; // Ill-formed
especially when the following example does
template<typename T, typename U> struct TU_ { TU_{T t, U u} : t{t}, u{u} {}; T t; U u; };
TU_<int, double> tu{1, 2.3}; // OK
TU_ tu2{1, 2.3}; // OK. TU_<int, double>
We propose that deduction takes place from the arguments. Rather than a deduction guide per se, this will need to use the same magic that aggregate initializers use to support the particular notations for aggregate initialization like designated initializers.
template<typename T, typename U> struct TUT { T t1; U u; T t2 = T{} };
TUT tut1 = { 1, 2.3, 2 }; // TUT<int, double>{1, 2.3, 2}
TUT tut2 = {.t1 = 3, u = 2.3 }; // TUT<int, double>{3, 2.3, 0}
template <typename T> struct A { A(T t); /* ... */ };
template <typename T>
auto makeA(T t) -> A<T> { return t; }
auto makeA(double) = delete;
auto makeA(int) -> A<int> = delete;
template <> makeA<long> -> A<long> = delete;
It seems very odd that ordinary template functions can be deleted but deduction guides cannot. Indeed, this was proposed in Template argument deduction for class templates (Rev. 7) with a vote in favor of adding to C++17 as a DR (we would also be OK with C++20 as a ship vehicle). For consistency, the same rules should apply as for deleting function templates.
If we imagine a slightly simplified version of unique_ptr that had a unique_ptr<T>::unique_ptr(T *) constructor, this would yield a type-unsafe deduction of T as int when deducing from an int * even if int[] would be correct. As a result, library writers would have to resort to ugly workarounds such as deducing an invalid type like unique_ptr<void>. Allowing deduction guides to be deleted would provide a clear and uniform solution to such situations with either of the following:
template <typename T> unique_ptr(T *) = delete; // OK
template <typename T> unique_ptr(T *) -> unique_ptr<T> = delete; // Also OK
template <typename T>
wrapping_reverse_iterator(wrapping_reverse_iterator<T> const &) -> wrapping_reverse_iteratory<T> = default delete;
The deduction guide must exactly match the existing (implicitly-generated) deduction guide that it is obliterating, including explicitly specifying the return type. (This seems most consistent with the existing rules for deleting specialization of function templates).
We believe this would be valuable for general functions and function templates, not just deduction guides. For example, a hypothetical wrapping version of any would like to remove the copy constructor from overload resolution.
struct wrapping_any {
wrapping_any(wrapping_any const &) = default delete;
/* ... */
};
any a{3}; // contains an int
any b{a}; // also contains an int
wrapping_any wa{3}; // contains an int
wrapping_any wb{wa}; // contains a wrapping_any
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, it is worth noting that the proposed deletion mechanisms would have allowed us to make any other choice desired. For example, 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;
Likewise, supposed we had wished to deduce valarray<int *>. We could accomplish this as follows:
template <T> valarray(const T *, size_t) -> valarray<T> = default delete;
In C++17, we deferred allowing CTAD to be applied in declarations with partially-specialized template argument lists out of concern that interactions with default arguments could create a breaking change. Consider the following example
template <typename T, typename U = T> struct A { A(T t, U u = U{}) {} };
template <typename T, typename U = T> A<T, U> makeA(T t, U u = U{}) { return A(t, u); }
auto a1 = A<int>(2, 3.5); // C++17: A<int, int>
auto a2 = makeA<int>(2, 3.5); // A<int, double>
To add support for partially-specialized template argument lists in declarations, we would have to decide what we want A<int>(2, 3.5) to deduce. The options are
We can compare these approaches with some motivating examples (assuming the deduction guides like those in P0433R3)
map<string, int> caseInsensitiveWordCounts(boost::algorithm::ilexicographic_compare{});
auto v = vector<int>(MyAlloc<int>{});
After considering examples such as the above, we propose adopting approach 2 for the following reasons.
vector v = {1, 2, 3}; // vector <int>
However, equally desirable code for pmr::vector fails because pmr::vector is an alias template and not a class template
pmr::vector v = {1, 2, 3}; // Ill formed. pmr::vector is not a class template
Furthermore, there is no way to perform CTAD for pmr::vector within C++17 language rules because alias templates cannot use CTAD. We believe that examples such as this motivate supporting CTAD for alias templates. We do note as a bikeshed that Class Template Argument Deduction is a misnomer for alias templates.
template <typename T> struct A { A(T); };
template <typename T> struct B : public A<T> { using A<T>::A; };
A a{3}; // Ill-formed. Inherited constructors do not implictly define deduction guides
This can make creating thin wrappers for classes (e.g., to just override a single method) cumbersome and error-prone, especially since the author of the derived class may need to manually replicate not only all the user-defined deduction guides of the base class but also all of the base class' implicitly-defined deduction guides as well. As a result, we propose that inheriting constructors from a base class also inherits their implicit and explicit deduction guides as well.Of course, this only applies in cases where the derived classes template arguments are determined from the base class' template arguments. For example,
template<typename T> struct B { B(T t); /* ... */};
template<typename T, typename U> struct D : B<U> {
using B<U>::B;
T t{};
};
B b{7}; // B<int>
D d{7}; // Ill-formed
D<double> d2{7}; // D<double, int> by CTAD proposal for partially-specialized argument lists above
We suspect the feature will be useful and that counterexamples such as struct D will be the exception since inherited constructors also tend to require (admittedly with some differences) that the construction of the derived class be largely determined by the construction of the base class. Indeed, the counterexample above was constructed through the at least somewhat esoteric technique of combining inherited constructors with non-static data member initializers for members of dependent types.