User:Mwe
C-Programming and Teaching since >30 years, C++ >20 years now.
My first C compiler came with Unix (XENIX), my first C++ compiler I bought from Comeau Computing in the early '90s - no g++ then, Zortech had a product for Windows but I was strickly *ix-ish then and still am. This compiler had neither templates, nor exceptions, nor a decent standard library for anything else but input and output.
The C++ world was neatly arranged then, nevertheless P.J. Plauger once (1993) wrote in the C/C++ Users Journal:
I confess that I still don't "get" C++ with anywhere near the depth that I understand C. I suspect I never will.
I often think of this when I start feeling a bit lost in certain areas of modern C++ (like template meta programming) ... at times when I find the language and its library overwhelming and impenetrable given the size it has reached ...
Trying the "look & feel" of the function template page with examples that compile:
To determine which of any two function templates is more specialized, the partial ordering process first transforms one of the two templates as follows:
- For each type, non-type, and template parameter, including parameter packs, a unique fictitious type, value, or template is generated and substituted into function type of the template
- If only one of the two function templates being compared is a non-static member of some class
A
, a new first parameter is inserted into its parameter list, whose type iscv A&&
if the member function template is &&-qualified andcv A&
otherwise (cv is the cv-qualification of the member function template) -- this helps the ordering of operators, which are looked up both as member and as non-member functions:
#include <iostream> struct A { template<class R1> int operator*(R1&) { std::cout << "#1\n"; return {}; } }; template<class L, class R2> int operator*(L&, R2&) { std::cout << "#2\n"; return {}; } struct B {}; int main() { A a; B b; a * b; // template argument deduction for int A<Z>::operator*(R1&) gives R1=B // for int operator*(L&, R2&), L=A, R2=B // For the purpose of partial ordering, the member template A::operator* // is transformed into template<class R1> int operator*(A&, R1&); // partial ordering between // int operator*(A&, R1&) R1=B // and int operator*(L&, R2&) L=A, R2=B // selects int operator*(A<Z>&, R1&) as more specialized // RESULT: calls #1 }
After one of the two templates was transformed as described above, [[../template argument deduction|template argument deduction]] is executed using the transformed template as the argument template and the original template type of the other template as the parameter template. The process is then repeated using the second template (after transformations) as the argument and the first template in its original form as the parameter.
The types used to determine the order depend on the context:
- in the context of a function call, the types are those function parameter types for which the function call has arguments (default function arguments, parameter packs, and ellipsis parameters are not considered -- see examples below)
- in the context of a call to a user-defined conversion function, the return types of the conversion function templates are used
- in other contexts, the function template type is used
Each type from the list above from the parameter template is deduced. Before deduction begins, each parameter P
of the parameter template and the corresponding argument A
of the argument template is adjusted as follows:
- If both
P
andA
are reference types before, determine which is more cv-qualified (in all other cases, cv-qualificiations are ignored for partial ordering purposes) - If
P
is a reference type, it is replaced by the type referred to - If
A
is a reference type, it is replaced by the type referred to - If
P
is cv-qualified,P
is replaced with cv-unqualified version of itself - If
A
is cv-qualified,A
is replaced with cv-unqualified version of itself - If
A
was transformed from a function parameter pack andP
is not a parameter pack, deduction fails
After these adjustments, deduction of P
from A
is done following [[../template argument deduction#Deduction_from_a_type|template argument deduction from a type]].
If the argument A
of the transformed template-1 can be used to deduce the corresponding parameter P
of template-2, but not vice versa, then this A
is more specialized than P
with regards to the type(s) that are deduced by this P/A pair.
If deduction succeeds in both directions, and the original P
and A
were reference types, then additional tests are made:
- If
A
was lvalue reference andP
was rvalue reference, A is considered to be more specialized than P - If
A
was more cv-qualified thanP
, A is considered to be more specialized than P
In all other cases, neither template is more specialized than the other with regards to the type(s) deduced by this P/A pair.
After considering every P and A in both directions, if, for each type that was considered,
- template-1 is at least as specialized as template-2 for all types
- template-1 is more specialized than template-2 for some types
- template-2 is not more specialized than template-1 for any types OR is not at least as specialized for any types
Then template-1 is more specialized than template-2. If the conditions above are true after switching template order, than template-2 is more specialized than template-1. Otherwise, neither template is more specialized than the other.
If, after considering all pairs of overloaded templates, there is one that is unambiguously more specialized than all others, that template's specialization is selected, otherwise compilation fails.
In the following examples, the fictitious arguments will be called U1, U2
#include <iostream> template<class T> void f(T) { std::cout << "#1\n"; } template<class T> void f(T*) { std::cout << "#2\n"; } template<class T> void f(const T*) { std::cout << "#3\n"; } int main() { const int* p; f(p); // overload resolution picks: #1: void f(T ) [T = const int *] // #2: void f(T*) [T = const int] // #3: void f(const T *) [T = int] // partial ordering // #1 from transformed #2: void(T) from void(U1*): P=T A=U1*: deduction ok: T=U1* // #2 from transformed #1: void(T*) from void(U1): P=T* A=U1: deduction fails // => #2 is more specialized than #1 with regards to T // #1 from transformed #3: void(T) from void(const U1*): P=T, A=const U1*: ok // #3 from transformed #1: void(const T*) from void(U1): P=const T*, A=U1: fails // => #3 is more specialized than #1 with regards to T // #2 from transformed #3: void(T*) from void(const U1*): P=T* A=const U1*: ok // #3 from transformed #2: void(const T*) from void(U1*): P=const T* A=U1*: fails // => #3 is more specialized than #2 with regards to T // (combined) => f(const T*) is more specialized than f(T) or f(T*) // RESULT: #3 is called }
Partial ordering of a type explicitely specified and a type infered from a deduced type is equivalent. So in the following example the call is ambigous.
#include <iostream> template<class T> struct S { T* p; S(T* p_) : p(p_) {} operator T*() { return p; } }; template<class T> void f(T, T*) { std::cout << "#1\n"; } template<class T> void f(T, int*) { std::cout << "#2\n"; } int main() { int i; f(i, &i); // deduction for #1: void f(T, T*) [T = int] // deduction for #2: void f(T, int*) [T = int] // partial ordering: // #1 from #2: void(T,T*) from void(U1,int*): P1=T, A1=U1: T=U1 // P2=T*, A2=int*: T=int: fails // #2 from #1: void(T,int*) from void(U1,U2*): P1=T A1=U1: T=U1 // P2=int* A2=U2*: fails // => neither is more specialized with regards to T // RESULT: the call is ambiguous }
#include <iostream> template<class T> void g(T) { std::cout << "#1\n"; } template<class T> void g(T&) { std::cout << "#2\n"; } int main() { float x; g(x); // deduction from #1: void g(T ) [T = float] // deduction from #2: void g(T&) [T = float] // partial ordering // #1 from #2: void(T) from void(U1&): P=T, A=U1 (after adjustment), ok // #2 from #1: void(T&) from void(U1): P=T (after adjustment), A=U1: ok // => neither is more specialized with regards to T // RESULT: the call is ambiguous }
#include <iostream> template<class T> struct A { A(){} }; template<class T> void h(const T&) { std::cout << "#1\n"; } template<class T> void h(A<T>&) { std::cout << "#2\n"; } int main() { A<int> z; h(z); // deduction from #1: void h(const T &) [T = A<int>] // deduction from #2: void h(A<T> &) [T = int] // partial ordering // #1 from #2: void(const T&) from void(A<U1>&): P=T A=A<U1>: ok T=A<U1> // #2 from #1: void(A<T>&) from void(const U1&): P=A<T> A=const U1: fails // #2 is more specialized than #1 with respect to T // RESULT: #1 is called const A<int> z2; h(z2); // deduction from #1: void h(const T&) [T = A<int>] // deduction from #2: void h(A<T>&) [T = int], but substitution fails // only one overload to choose from, partial ordering not tried // RESULT: #1 is called }
Since in a call context considers only parameters for which there are explicit call arguments, some parameters are ignored:
The following actually compiles though in its comment says the result is ambiguous.
The following actually compiles though in its comment says the result is ambiguous.
#include <iostream> template<class... Args> void f(Args...) { std::cout << "#1\n"; } template<class T1, class... Args> void f(T1, Args...) { std::cout << "#2\n"; } template<class T1, class T2> void f(T1, T2) { std::cout << "#3\n"; } int main() { f(); // calls #1 f(1, 2, 3); // calls #2 f(1, 2); // calls #3 (non-variadic template #3 is more specialized // than variadic templates #1 and #2) }
During template argument deduction within the partial ordering process, template parameters don't require to be matched with arguments, if the argument is not used in any of the types considered for partial ordering
#include <iostream> template <class T> T f(int) { std::cout << "#1\n"; return {}; } template <class T, class U> T f(U) { std::cout << "#2\n"; return {}; } int main() { f<int>(1); // specialization of #1 is explicit: T f(int) [T = int] // specialization of #2 is deduced: T f(U) [T = int, U = int] // partial ordering (only considering the argument type) // #1 from #2: T(int) from U1(U2): fails // #2 from #1: T(U) from U1(int): ok: U=int, T unused // RESULT: #1 is called }
Partial ordering of function templates containing template parameter packs is independent of the number of deduced arguments for those template parameter packs.
#include <iostream> template<class...> struct Tuple { }; template< class... Types> void g(Tuple<Types ...>) { std::cout << "#1\n"; } template<class T, class... Types> void g(Tuple<T, Types ...>) { std::cout << "#2\n"; } template<class T, class... Types> void g(Tuple<T, Types& ...>) { std::cout << "#3\n"; } int main() { g(Tuple<>()); // calls #1 g(Tuple<int, float>()); // calls #2 g(Tuple<int, float&>()); // calls #3 g(Tuple<int>()); // calls #3 }