Carousel Carousel - 3 months ago 17
C++ Question

How does substitution work in template argument deduction?

C++ standard 14.8.2$7 says that:


The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (i.e., non-constant expressions) inside
sizeof
,
decltype
, and other contexts that allow non-constant expressions. The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered. [ Note: The equivalent substitution in exception specifications is done only when the exception-specification is instantiated, at which point a program is ill-formed if the substitution results in an invalid type or expression. — end note ]


The standard provides an example here:

template <class T> struct A { using X = typename T::X; };
template <class T> typename T::X f(typename A<T>::X);
template <class T> void f(...) { }
template <class T> auto g(typename A<T>::X) -> typename T::X;
template <class T> void g(...) { }

void h() {
f<int>(0); // OK, substituting return type causes deduction to fail
g<int>(0); // error, substituting parameter type instantiates A<int>
}


Why calling
g<int>(0)
is an error here? Doesn't the trailing-return-type
T::X
cause substitution failure? What's the difference between the template function
f
and
g
?

Answer

The key points are, first,

The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered

And second, instantiation of A<int>'s definition triggers a hard error, not a substitution failure, because that results in instantiating an ill-formed construct typename T::X (with T == int) outside the immediate context. [temp.deduct]/8:

Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure. [ Note: The evaluation of the substituted types and expressions can result in side effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such side effects are not in the “immediate context” and can result in the program being ill-formed. — end note ]

With the templates at issue, substituting into typename T::X in the function signature results in a deduction failure (i.e., SFINAE); substituting into typename A<T>::X results in a hard error. Since substitution proceeds in lexical order, for template <class T> typename T::X f(typename A<T>::X); it substitutes into typename T::X first, resulting in a deduction failure, and further substitution is not attempted. For template <class T> auto g(typename A<T>::X) -> typename T::X;, on the other hand, it substitutes into typename A<T>::X first, which results in a hard error.

Comments