Let's talk templates!





Let’s refresh our rather rusty C++ starting with one of the language’s more commonly used feature; one which also differentiates it from good old C! Aptly named “templates”, it is usually used for generating code on the fly. A rather typical scenario would include having a templatized version of a function which can then be used with different arguments. Since this (multiple functions with the same name having differing argument types) is valid C++, it saves us a bunch of typing.

Without further ado, let’s see how it looks!

#include <iostream>

template<typename T>
void swap(T &a, T &b) {
  T temp;

  temp = a;
  a = b;
  b = temp;
}

int main() {
    using namespace std;

    int a,b;

    a = 7, b = 10;

    cout << "a is " << a << " while b is " << b << endl;
    swap(a,b);
    cout << "a is " << a << " while b is " << b << endl;
    
    return 0;
}

As can be seen, the keyword template defines a template function. There’s no point to a template without a placeholder who’s type will be determined at run-time. This is exactly what the statement typename T does. Once this placeholder has been defined, we can use it just like any other type. Since we’re just getting (re)started with C++, let’s keep it simple with just one type.

Compiling this as is will give us a compile time error! gcc (v13) tell us that

<source>: In function 'int main()':
<source>:21:9: error: call of overloaded 'swap(int&, int&)' is ambiguous
   21 |     swap(a,b);
      |     ~~~~^~~~~
In file included from /opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/bits/exception_ptr.h:41,
                 from /opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/exception:164,
                 from /opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/ios:41,
                 from /opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/ostream:40,
                 from /opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/iostream:41,
                 from <source>:2:
/opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/bits/move.h:196:5: note: candidate: 'std::_Require<std::__not_<std::__is_tuple_like<_Tp> >, std::is_move_constructible<_Tp>, std::is_move_assignable<_Tp> > std::swap(_Tp&, _Tp&) [with _Tp = int; _Require<__not_<__is_tuple_like<_Tp> >, is_move_constructible<_Tp>, is_move_assignable<_Tp> > = void]'
  196 |     swap(_Tp& __a, _Tp& __b)
      |     ^~~~
<source>:5:6: note: candidate: 'void swap(T&, T&) [with T = int]'
    5 | void swap(T &a, T &b) {
      |      ^~~~

It appears the template library (a collection of powerful functions pre-defined for our use) already implements a swap function. Which is why our function here is not allowed (as conflict ensues, literally!). For it to work, we need to replace swap with Swap (mind you C++, like C, is case sensitive). Once done, it compiles and then we can use it with any kind of arguments (provided both a and b are interoperable).

Let’s see how the resulting binary would look like when used with a good ‘ol integer. Running objdump on it with the -C switch (for unmangling the decorated function names, which the g++ compiler does) we see this (partial output for brevity):

000000000000136f <void Swap<int>(int&, int&)>:
    136f:       f3 0f 1e fa             endbr64
    1373:       55                      push   %rbp
    1374:       48 89 e5                mov    %rsp,%rbp
    1377:       48 89 7d e8             mov    %rdi,-0x18(%rbp)
    137b:       48 89 75 e0             mov    %rsi,-0x20(%rbp)
    137f:       48 8b 45 e8             mov    -0x18(%rbp),%rax
    1383:       8b 00                   mov    (%rax),%eax
    1385:       89 45 fc                mov    %eax,-0x4(%rbp)
    1388:       48 8b 45 e0             mov    -0x20(%rbp),%rax
    138c:       8b 10                   mov    (%rax),%edx
    138e:       48 8b 45 e8             mov    -0x18(%rbp),%rax
    1392:       89 10                   mov    %edx,(%rax)
    1394:       48 8b 45 e0             mov    -0x20(%rbp),%rax
    1398:       8b 55 fc                mov    -0x4(%rbp),%edx
    139b:       89 10                   mov    %edx,(%rax)
    139d:       90                      nop
    139e:       5d                      pop    %rbp
    139f:       c3                      ret

We thus see that the template was filled out and replaced with an actual Swap function that takes two integer references as input!