• 1.1 Defining function templates
  • 1.2 Using function Templates
  • 1.3 Two-phase Translation
    • 1.3.1 Template compilation and linking issues
  • 1.4 Multiple Template Parameters
    • 1.4.1 Introduce additional template parameters as return value types
    • 1.4.2 Let the compiler figure out the return value type itself
    • 1.4.3 Declare the return value as a common type for both template parameters
  • 1.5 Default Template Parameters
  • 1.6 Overloading function templates
    • 1.6.1 When overloading, it is better not to change the number of template parameters casually. It is better to display the specified template parameter type
    • 1.6.2 Ensure that all overloaded function templates are declared when used

1.1 Defining function templates

template<typename T>
T max(T a,T b) {
  return b < a ? a : b;
}
Copy the code

1.2 Using function Templates

  std::cout << max(7.42) << std::endl;

  std::cout << max(1.1.2.2) << std::endl;
  
  std::cout << max("math"."mathematics") << std::endl;
Copy the code

Templates are not compiled to handle a single function of any type. Instead, the compiler generates a function for each type that uses the template. For example, a call to Max (7,42) is semantically equivalent to a call:

int max(int a,int b) {
  return b < a ? a : b;
}
Copy the code

The same goes for double and string.

The process of replacing template parameters with specific parameter types is calledinstantiationThe process produces oneinstance of template.

1.3 Two-phase Translation

Compile-time errors are reported if a particular parameter type does not support operations within the template, for example:

std::complex<float> c1,c2;        // The < operation in Max is not supported, and an error is reported at compile time.max(c1,c2);
Copy the code

Templates are compiled in two stages:

  1. Do not template ininstantiationthedefinition timeIn this case, the template parameters are ignored and the following aspects are checked:
    • Syntax errors, including missing semicolons.
    • Use undefined parameters.
    • If static Assertion does not depend on template parameters, static Assertion is checked.
  2. ininstantiationPhase, all code in the template is checked again for correctness, especially those parts that depend on template parameters.

Such as:

template<typename T>
void foo(T t) {
  undeclared(a);// first-phase compile-time error if undeclared() unknown

  undeclared(t);       // second-phase compile-time error if undeclared(T) unknown

  static_assert(sizeof(int) > 10."int too small");      // first-phase compile-time error

  static_assert(sizeof(T) > 10."T too small");        // second-phase compile-time error

}
Copy the code

1.3.1 Template compilation and linking issues

Most people would organize non-template code like this:

  • Put class or other type declarations in header files (.hpp,.h,.h,.hh,.hxx).
  • Put function definitions, etc., in a separate compilation unit file (.cpp,.c,.c,.cc,.cxx).

But this does not work in code that contains templates, such as header files:

// myfirst.hpp
#ifndef MYFIRST_HPP
#define MYFIRST_HPP
// declaration of template
template<typename T>
void printTypeof (T const&);
#endif // MYFIRST_HPP
Copy the code

The file that defines the function template:

// myfirst.cpp
#include <iostream>
#include <typeinfo>
#include "myfirst.hpp"
// implementation/definition of template
template<typename T>
void printTypeof (T const& x) {
  std::cout << typeid(x).name() < <'\n';
}
Copy the code

Use this template in another file:

// myfirstmain.cpp
#include "myfirst.hpp"
// use of the template
int main(a) {
  double ice = 3.0;
  printTypeof(ice); // call function template for type double
}
Copy the code

In C/C ++, when a symbol (printTypeof) is not defined but declared at compile time, the compiler assumes that its definition is in another file, so the compiler leaves a “pit” for the linker to fill in the real symbol address.

However, as mentioned above, templates are special and need to be instantiated at compile time, i.e. template parameter type inference, template instantiation, and of course function definition. But since both CPP files are separate compilation unit files, when the compiler compiles myFirstmain.cpp, it does not find the template definition and is not instantiated.

The solution is to put the template declaration and definition in a header file. If you look at STL source files such as vector in your environment, you can put the class declaration and definition in one file.

1.4 Multiple Template Parameters

template<typename T1, typename T2>
T1 max (T1 a, T2 b) {
    returnb < a ? a : b; }...auto m = max(4.7.2);       // Note that the return type is the type of the first template parameter T1
Copy the code

But the problem is that the return type of Max is always T1, as noted in the comment. If we call Max (42, 66.66), the return value is 66.

There are generally three ways to solve this problem:

  • Introduce additional template parameters as return value types
  • Let the compiler figure out the return value type itself
  • Declare the return value as the common type of the two template parameters, such as int and float, which is float

1.4.1 Introduce additional template parameters as return value types

In the derivation of function template parameter types, we generally do not specify template parameter types explicitly. However, when template parameters cannot be deduced from the passed parameters, we need to specify template parameter types explicitly.

template<typename T1, typename T2, typename RT>
RT max(T1 a, T2 b);
Copy the code

RT cannot be derived from the argument list of a function, so we need to specify it explicitly:

max<int.double.double> (4.7.2);
Copy the code

Or we can change the order of the template argument list, in which case we only need to explicitly specify a parameter type:

template<typename RT typename T1, typename T2>      //RT becomes the first template argument
RT max(T1 a, T2 b); . max<double> (4.7.2);
Copy the code

1.4.2 Let the compiler figure out the return value type itself

In C++11, we can use auto and trailing return type to tell the compiler to find the return value type:

template <typename T1, typename T2>
auto max(T1 a, T2 b) -> decltype(b < a ? a : b) {
  return b < a ? a : b;
}
Copy the code

Decltype will be covered later in this article, but it only needs to be known that it can get the type of the expression.

We can write it even simpler:

template <typename T1, typename T2>
auto max(T1 a, T2 b) -> decltype(true ? a : b) {      // true ? a : b
  return b < a ? a : b;
}
Copy the code

About? The: return value rule can refer to this: Conditional Operator:? :

See true? A: b Don’t wonder why true, the point here is not to calculate the return value, but to get the return value type.

In C++14, we can omit the trailing return type:

template<typename T1, typename T2>
auto max (T1 a, T2 b) {
    return b < a ? a : b;
}
Copy the code

1.4.3 Declare the return value as a common type for both template parameters

The new c++11 feature STD ::common_type can produce several different types of common types.

template <typename T1, typename T2>
typename std::common_type<T1, T2>::type max(T1 a, T2 b) {
  return b < a ? a : b;
}
Copy the code

In c++14, it is easier to write:

template <typename T1, typename T2>
std::common_type_t<T1, T2> max(T1 a, T2 b) {     
  return b < a ? a : b;
}
Copy the code

Using the _t suffix saves us from writing typename and ::type. Similarly, _v is common in c++14’s type traits.

1.5 Default Template Parameters

This looks like the default argument to a function.

template <typename T1, typename T2, typename RT = std::common_type_t<T1, T2>>
RT max(T1 a, T2 b) {
  return b < a ? a : b;
}

auto a = max(4.7.2);
auto b = max<double.int.long double> (7.2.4);
Copy the code

As in the second usage, if we want to display the type of RT, we must display all three parameter types. But unlike the function default argument, we can put the default argument in the first place:

template <typename RT = long.typename T1, typename T2> 
RT max(T1 a, T2 b) {
  return b < a ? a : b;
}

int i;
longl; ...max(i, l);                     // Return value type is long (RT default)
max<int> (4.42);      // Returns int because it is explicitly specified
Copy the code

1.6 Overloading function templates

This is similar to normal function overloading:

// maximum of two int values:
int max(int a, int b) { 
  return b < a ? a : b; 
}

// maximum of two values of any type:
template <typename T> 
T max(T a, T b) { 
  return b < a ? a : b; 
}

int main(a) {
  max(7.42);         // calls the nontemplate for two ints
  max(7.0.42.0);     // calls max<double> (by argument deduction)
  max('a'.'b');      // calls max<char> (by argument deduction)
  max<>(7.42);       // calls max<int> (by argument deduction)
  max<double> (7.42); // calls max<double> (no argument deduction)
  max('a'.42.7);     // calls the nontemplate for two ints
}
Copy the code

The last Max (‘a’, 42.7) needs to be explained here. Because automatic type conversions are not allowed during template argument type derivation, but normal function calls are allowed, this calls non-template functions.

Ps. Function templates are not specialized like class templates because they are overloaded.

There are two more basic rules to know about overloading:

1.6.1 When overloading, it is better not to change the number of template parameters casually. It is better to display the specified template parameter type

Here is the offending code:

// maximum of two values of any type (call-by-reference)
template <typename T> T const &max(T const &a, T const &b) {
  return b < a ? a : b;
}

// maximum of two C-strings (call-by-value)
char const *max(char const *a, char const *b) {
  return std::strcmp(b, a) < 0 ? a : b;
}

// maximum of three values of any type (call-by-reference)
template <typename T> T const &max(T const &a, T const &b, T const &c) {
  return max(max(a, b), c);           // error if max(a,b) uses call-by-value
}

int main(a) {
  auto m1 = max(7.42.68);         // OK

  char const *s1 = "frederic";
  char const *s2 = "anica";
  char const *s3 = "lucas";
  auto m2 = max(s1, s2, s3);         // run-time ERROR
}
Copy the code

Return Max (Max (a,b), c); Because char const * Max (char const *a, char const *b) is passed by value, Max (a,b) produces an address pointing to the destroyed stack frame, which results in undefined behavior.

1.6.2 Ensure that all overloaded function templates are declared when used

// maximum of two values of any type:
template <typename T> 
T max(T a, T b) {
  std::cout << "max<T>()\n";
  return b < a ? a : b;
}

// maximum of three values of any type:
template <typename T> 
T max(T a, T b, T c) {
  return max(max(a, b), c); 
}

// maximum of two int values:
int max(int a, int b) {
  std::cout << "max(int,int) \n";
  return b < a ? a : b;
}

int main(a) {
  max(47.11.33);    // max<T>()
}
Copy the code

This is easy to understand.

(after)

Friends can follow my public account, get the most timely updates: