c++ templates: day 1
Tags: cpp, beginners
Published: 2024-01-15 05:15:47

Have used template for years, today I've decided to start a thorough cpp template study from the beginning. In this series, I will update some bullet points in my study progress and post questions. 

The book I am reading is *C++ Templates: The Complete Guide, 2nd edition*.

Now, let's get started.

## a. Use `int const` Instead of `const int`

For years I've used `const int`, but reading from **right to left** may help us understand most simple cases better.

A common example:
- `char const *` read from right to left: `*` indicates it is a pointer; `char const` indicates that the pointer is pointing to a `const char`.
- `char * const`: `const` indicates the value is constant; `char *` indicates that the const value is a pointer pointing to char.

Although I've known this technique for years, I'm still using `const int` in my everyday work. Hummm...

## b. Why Templates?

In the book, the author has mentioned three bad alternatives to templates:
- Repetitive Implementation for different types
- Use `Object` or `void *`
- Third-party code generator

Method 1 and 3 are apparently stupid. Method 2 will make the code lose the type checking and make the code hard to maintain (although I think template is hard to maintain as well lol).

In addition to the reasons above, I think another important reason to use template is that it can help us avoid many `if` clauses in which the condition is only decided by the code content (instead of runtime input). 

## c. Use `typename` in Templates

The keyword *typename* came relatively later than *class*, so *class* is still supported because of historical reason. However, if you are writing new cpp library, please use *typename* since not only class types can be used for substitution.

## d. Template Argument Deduction

When we call a function template for some arguments, the types of parameters are determined by the arguments we pass. It is easy to understand when the case is simple.

However, there may be some complex cases. For example:

```c
template<typename T>
T example1(T const &a) { ... }

template<typename T>
T example2(T a) { ... }
```

In this example, the template call parameter of `example1` are declared by reference whilst the ones of `example2` are declared by value.

The automatic type conversion rules are different for those two cases:
- When call parameters are declared by reference, there will not be any type conversion.
- When call parameters are declared by value, then `const` and `volatile` keywords will be ignored, references will be converted to the referenced type, and raw arrays or functions will be converted to the corresponding pointer type.

For example:

```c
int const c = 42;
example2(c); // T is deduced as int; `const` has been ignored
int &test = 1;
example2(test); // T is deduced as int
int arr[4];
example2(arr); // T is deduced as int *
```

## e. Declare Return Type with Multiple Template Parameters

## e.1. Use `auto` as the Return Type

Since C++14, it is possible to let the compiler help find out and deduce the return type by using keyword `auto`:

```c
template<typename T1, typename T2>
auto max(T1 a, T2 b) {
  return b < a ? a : b;
}
```

However, the drawback of this technique is that the template function has to be implemented when declared (which means the definition and declaration have to be put together).

Fortunately, since C++11 we can benefit from the *trailing return type*, which allows us to use the call parameters to explicitly declare the return type. For example:

```c
template<typename T1, typename T2>
auto max(T1 a, T2 b) -> decltype(true?a:b);
```

There is another problem to this template function. If the template parameter is a reference type, then the return type will be deduced to reference, which is not desired. Hence, we need to *decay* the return type, which is like:

```c
template<typename T1, typename T2>
auto max(T1 a, T2 b) -> std::decay_t<decltype(true?a:b)>;
```

## e.2. Use `std::common_type<>`

```c
template<typename T1, typename T2>
std::common_type<T1, T2> max(T1 a, T2 b);
```

Note that std::common_type<> also *decays*.

## e.3 Combine with Default Template Arguments

**Combine with decltype:**

```c
template<typename T1, typename T2, typename RT = std::decay_t<decltype(true ? T1() : T2())>
RT max(T1 a, T2 b);
```

**Combine with `std::common_type<>`**:

```c
template<typename T1, typename T2, typename RT = std::common_type_t<T1, T2>>
RT max(T1 a, T2 b);
```

## Questions

1. I am still confused why do we need to use *trailing return type* when doing return type deduction. In most cases, the definition of template functions will be put together. It is only useful when the definition of template function is separated into the source cpp file with explicit instantiations for specific types provided. Maybe for some close source libraries with IP will need this feature.