Namespaces
Variants
Views
Actions

Talk:cpp/language/function template

From cppreference.com

[edit] Merging function/class template

Why two separate pages? Templates work mostly the same in classes and functions. -- Bazzy 10:12, 16 March 2012 (PDT)

These pages are probably just placeholders. Once we have some content, it would be much clearer whether the pages should be merged or not. My personal opinion is that there's a lot of information about templates, so it's probably beneficial to present it in several pages, though this may not apply to this case. Anyway, we'll see the big picture once the pages are filled.-- P12 19:00, 16 March 2012 (PDT)

[edit] Examples that compile

May I suggest that examples for this topic should be complete to be compiled too?

The major benefit is that you can easily test and try your understanding by making small modifications.

Surely it adds some lines but I think if done in a minimalistic way the core of each example will still be clearly visible - sew below:

#include <iostream>
template< class T > void f(T)         { std::cout << "#1\n"; }  // template overload
template< class T > void f(T*)        { std::cout << "#2\n"; }  // template overload
void                     f(double)    { std::cout << "#3\n"; }  // nontemplate overload
template<>          void f(int)       { std::cout << "#4\n"; }  // specialization of #1
 
int main() {
    int v;
    f('a');     // calls #1
    f(&v);      // calls #2
    f(1.0);     // calls #3
    f(1);       // calls #4
}


Here is another one - if this style is acceptable, I'd volunteer to change all the examples in the topic's page accordingly.

#include <iostream>
template< class T > void f(T)     { std::cout << "#1\n"; }    // overload for all types
template<>          void f(int*)  { std::cout << "#2\n"; }    // specialization of #1 for pointers to int
template< class T > void f(T*)    { std::cout << "#3\n"; }    // overload for all pointer types
 
int main() {
    int v;
    f(&v);      // calls #3, even though specialization of #1 would be a perfect match
}


Mwe (talk) 15:50, 26 May 2014 (PDT)

I feel that I went a bit overboard with the examples on this page: although they generally reflect what the standard found necessary to show in examples, a few should be dropped. In any case, the expected layout on this wiki so far has been is one compilable example in the end of the article, and occasional code snippets inline where necessary. Perhaps you're right, code snippets can be made compilable without adding too much bloat, I'd let others weigh in. --Cubbi (talk) 18:59, 26 May 2014 (PDT)
I'm okay with simple, short code snippets that do not compile -- I'd imagine that in that case, adding boilerplate to make the code compilable would do more harm than good, as it could interrupt the flow and obfuscate the point that's trying to be made. However, that argument doesn't hold up so well for longer examples. My feeling is there is some length threshold above which it makes sense to start making the examples compilable. For this page, maybe it would make sense to start with some longer examples and see how they look? --Nate (talk) 07:41, 27 May 2014 (PDT)

So that you can see here how it would look - these are the examples from the original page, with minor modifications to keep them short.

Please also note that after making this compile, two examples have not the outcome as described in their comments (I have marked them below).

So, no matter how this issue is decided, the topic page currently contradicts to the actual behavior and needs correction.

I did explicitely NOT show any output because the result is explained in the comment and the examples are mainly here for the reader to make small modifications and test his understanding.

-- Mwe (talk) 12:45, 27 May 2014 (PDT)

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
}


I added some lines here to show how also #2 might be called unambigously (comment the currently ambigous call.)

I don't think the additions to this example are helpful. The focus was on one particular case where partial ordering fails and why. --Cubbi (talk) 15:15, 27 May 2014 (PDT)
I agree with this --Mwe (talk) 16:04, 27 May 2014 (PDT)
#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() {
    char h[3] = "ab";
    f('a', h);        // calls #1 (T deduced to char)
    int i;
    f(i, S<int>(&i)); // calls #2 (T deduced to int, second argument is of type S<int>,
                      // therefore #1 does not match; #2 is selected and will finally
                      // succeed by applying the conversion (type cast) from S<int>
    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:

#include <iostream>
template<class T>  void  f(T)         { std::cout << "#1\n"; }
template<class T>  void  f(T*, int=1) { std::cout << "#2\n"; }
 
int main() {
    int* ip;
    f(ip);     // calls #2 (T* is more specialized than T)
}


#include <iostream>
template<class  T>  void  g(T)       { std::cout << "#1\n"; }
template<class  T>  void  g(T*, ...) { std::cout << "#2\n"; }
 
int main() {
    int *ip;
    g(ip);     // calls #2 (T* is more specialized than T)
}


#include <iostream>
template<class T, class U> struct A { };
template<class T, class U> void f(U, A<U,T>* = 0) { std::cout << "#1\n"; }
template<         class U> void f(U, A<U,U>* = 0) { std::cout << "#2\n"; }
 
int main() {
    f<int>(42, (A<int, int>*)0);  // calls #2
    f<int>(42);                   // error: ambiguous
}


The following actually compiles though in its comment says the result is ambiguous.

#include <iostream>
template<class T           >  void g(T, T = T()) { std::cout << "#1\n"; }
template<class T, class... U> void g(T, U ...)   { std::cout << "#2\n"; }
 
int main() {
    g(42);  // error: ambiguous (BUT ACTUALLY COMPILES with gcc-4.8 and clang-3.4)
}


The following actually compiles though in its comment says the result is ambiguous.

#include <iostream>
template<class  T, class... Args> void f(T, Args...) { std::cout << "#1\n"; }
template<class  T               > void f(T)          { std::cout << "#2\n"; }
 
int main() {
    int i;
    f(&i);        // error: ambiguous (BUT ACTUALLY COMPILES with gcc-4.8 and clang-3.4)
}


#include <iostream>
template<class  T, class... Args> void g(T*, Args...) { std::cout << "#1\n"; }
template<class  T               > void g(T)           { std::cout << "#2\n"; }
 
int main() {
    int i;
    g(&i);        // calls #1 (T* is more specialized than T)
}


#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
}


regarding "ambiguous (BUT ACTUALLY COMPILES with gcc-4.8 and clang-3.4" -- this is a C++ language reference, not any particular compiler reference. Don't get distracted by the bugs and language extensions available in the compilers provided by coliru. Those two examples, in particular, are directly from 14.5.6.2[temp.func.order]/5 --Cubbi (talk) 14:43, 27 May 2014 (PDT)
Specifically for this issue, compiler bug reports are gcc #41958 and clang #14372 --Cubbi (talk) 15:07, 27 May 2014 (PDT)
Thanks - I purposely put this here on the discussion page (only), this is no proposal it should go in the reference page, but (please correct me if I'm wrong): as I understand the discussion about core issue 1395 not preferring an ommitted parameter over a parameter pack (i.e. exactly what makes the first of the two examples ambiguous) has been identified as a language defect as it breaks a valid and useful pattern. Therefore we are not exactly talking about a broken compiler here. If I'm right, the question is whether we should reproduce this example uncommented because "it is as it is" if we only consider the words of the current standard or whether we rather should give a hint that compilers which do not identfify this as an ambiguity are simply forestalling an expected change of the language rules? --Mwe (talk) 16:04, 27 May 2014 (PDT)
It's fine to mention that in C++17, the behavior will be different due to CWG 1395, but there is currently nothing to back that up. Wait until the resolution is drafted, or better voted into the WP. --Cubbi (talk) 20:32, 27 May 2014 (PDT)