trainings/AdvancedCppV2/Presentation/templates_basic.md

6.7 KiB
Raw Permalink Blame History

Template functions


Examples

Let's assume that we have a function below:

int add(int first, int second) {
    return first + second;
}

If we want to have a function that takes doubles as well, we need to write:

double add(double first, double second) {
    return first + second;
}

And if we want a function that can take complex or any other numbers we would need to write:

std::complex<int> add(std::complex<int> first, std::complex<int> second) {
    return first + second;
}

You can clearly see that we have a code duplication here.


Avoiding code duplication

Instead of writing so many functions we can have only one - template function:

template <typename Type>
Type add(Type first, Type second) {
    return first + second;
}

Instead of Type, you can have any name you wish. Typically you will see just T as a typename, but it is better to have a longer name than only one character, especially, when there is more than only one template parameter. Now, you can use this function like this:

auto resultI = add<int>(4, 5);  // resultI type is int
auto resultD = add<double>(4.0, 5.0);  // resultD type is double
auto resultC = add<std::complex<int>>({1, 2}, {2, 3});  // resultC type is std::complex<int>

You can play with the code here


Function template type deduction

There is a function template types deduction in C++. It means that you can skip part with angle braces <> and write previous example like this:

auto resultI = add(4, 5);  // resultI type is int
auto resultD = add(4.0, 5.0);  // resultD type is double
auto resultC = add({1, 2}, {2, 3});  // error, does not compile

resultC will not compile, because in this case compiler will not know what is the type of {1, 2} or {2, 3}. std::initializer_list can never be a result of parameter type deduction in templates. In this case we have to type it explicitly:

auto resultC = add(std::complex<int>{1, 2}, std::complex<int>{2, 3});

or

auto resultC = add<std::complex<int>>({1, 2}, {2, 3});

Exercise

Write a function that creates std::complex number from two provided numbers. If the types of numbers are different, it should create std::complex of the first parameter. Usage:

std::complex<int> a = makeComplex(4, 5);        // both ints
std::complex<double> b = makeComplex(3.0, 2.0); // both doubles
std::complex<int> c = makeComplex(1, 5.0); // int, double -> takes int

Multiple template parameters

The compiler itself deduce which template function parameters should be used. However, if you write the code like this:

auto resultC = add(4, 5.0);  // error: int + double

We will have a compilation error. The compiler will not deduce parameter, because our template function takes only one type, and both parameters have to be of the same type. We can fix this by adding a new version of the template of add function.

template <typename TypeA, typename TypeB>
TypeA add(TypeA first, TypeB second) {
    return first + second;
}

Now the code should work:

auto resultC = add(4, 5.0);  // resultC type is int

The output type is the same as the first argument type because it was defined in the template function above as TypeA.


typeid

Generally, you can freely use template types inside functions. For example, you can create new variables of provided types:

#include <typeinfo>

template <class T>
void showType() {
    T value;
    std::cout << "Type: " << typeid(value).name() << std::endl;
}

You can use typeid().name() to print variable type. You need to include the typeinfo header for this. The output is implementation-defined.

You can also notice, that instead of the typename keyword, you can also use the class keyword. They are interchangeable.

template <typename T> == template <class T> != template <struct T>

No matching function

In previous case if you want to use showType() function without providing explicit templates, the code will not compile:

int main() {
    showType();
    return 0;
}
prog.cpp: In function int main():
prog.cpp:15:12: error: no matching function for call to showType()
  showType();
           ^
prog.cpp:7:6: note: candidate: template<class T> void showType()
 void showType()
      ^~~~~~~~~
prog.cpp:7:6: note:   template argument deduction/substitution failed:
prog.cpp:15:12: note:   couldn't deduce template parameter T
  showType();

Template function parameter type deduction

The compiler cannot deduce parameters, because the functions do not take any parameters. You need to provide the type explicitly:

int main() {
    showType<int>();
    return 0;
}

or

int main() {
    showType<std::vector<char>>();
    return 0;
}

You can also play with the code here


STL example

template<class InputIt, class UnaryPredicate>
InputIt find_if(InputIt first, InputIt last, UnaryPredicate p)
{
    for(; first != last; ++first) {
        if(p(*first)) {
            return first;
        }
    }
    return last;
}
std::vector<std::pair<int, int>> v{{-3, 1}, {2, 3}, {4, -5}};
auto it = std::find_if(begin(v), end(v), [](auto& e){ return e.first == 2; });
if(it != std::end(v)) {
    /* ... */
}