当前位置:网站首页>Function templatesfunction templates

Function templatesfunction templates

2022-06-12 02:25:00 loongknown

Start with this article , Yes C++ Templates The Complete Guide Second Edition Carry out systematic learning and interpretation .

This chapter explains the basic knowledge of template functions in Chapter 1 , The following chapters will go deeper .

A glimpse of function templates A First Look at Function Templates

A function template represents a family of functions , Some of its elements are parameterized .

Define function templates Defining the Template

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

The template function returns the maximum of the two input parameters , Two input parameters a and b The type of is template parameter T. Template parameters are declared in the following syntax :

template< comma-separated-list-of-parameters >

here , keyword typename Used to define template parameters . For historical reasons , You can also use keywords class Instead of typename To define template parameters . stay C++98 It used to be class Declare template parameters ,C++98 Introduced typename, To stay compatible , Either . therefore , The above code is equivalent to :

template<class T>
T max (T a, T b) {
    
  return b < a ? a : b;
}

however , To avoid and classes (class) The definition of , Recommended priority typename Define template type parameters .

In our example, the template parameter is T, Use T It is just a convention , You can also use other identifiers , for example U. The template parameters here can be of any type , As long as this type satisfies the operation of the template function . In the example above , type T Must support < operation . Besides , A less obvious limitation is : To be returned by the function ,T The value of type must be copyable .

Using function templates Using the Template

int main()
{
    
  int i = 42;
  std::cout << "max(7,i): " << ::max(7,i) << ’\n’;  // 42
  double f1 = 3.4;
  double f2 = -6.7;
  std::cout << "max(f1,f2): " << ::max(f1,f2) << ’\n’; // 3.4
  std::string s1 = "mathematics";
  std::string s2 = "math";
  std::cout << "max(s1,s2): " << ::max(s1,s2) << ’\n’;  // mathematics
  return 0;
}

Use here ::max Call function max Avoid confusion , Is to find in the global namespace max, Because this function is also defined in the standard library std::max.

Function calls with different parameter types will produce different function instances . The above examples generate intdouble and std::string 3 Instances of versions . for example ::max(7,i) , The compiler will generate parameters of type int Function instance of , Similarly, call the following code :

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

Be careful ,void It is also a valid template parameter type . for example :

template<typename T>
T foo(T*) {
    
}

void* vp = nullptr;
foo(vp); // OK: deduces void foo(void*)

A two-stage translation process Two-Phase Translation

Using parameter types that are not supported by operations in the template will generate compilation errors . for example :

std::complex<float> c1, c2; // doesn’t provide operator <
...
::max(c1,c2); // ERROR at compile time

Template translation / Compilation is divided into two stages : Occurs before and during instantiation respectively .

  • Check the code template code before instantiation , The template parameters are ignored . It mainly includes :
    • Whether there are grammatical errors . For example, whether semicolons are missing .
    • Whether to use unknown names that do not depend on template parameters ( Function name 、 Type name, etc ).
    • Check for... That do not depend on template parameters static assert.
  • Check the template code again during instantiation , In particular, the parts that depend on template parameters . For example, whether the instantiated parameter type in the above example supports < operation .
template <typename T>
void f(T x) {
    
  undeclared();   // first-phase compile-time error if undeclared() unknown
  undeclared(x);  // second-phase compile-time error if undeclared(T) unknown
  static_assert(sizeof(int) > 10,
                "int too small");  // always fails if sizeof(int) <= 10
  static_assert(sizeof(T) > 10,
                "T too small");  // fails if instantiated for T with size <= 10
}

Template parameter derivation Template Argument Deduction

When we call a template function with parameters ( such as max()), The template parameters are determined by the parameters we pass . If we pass two int Give template functions , The compiler will derive T by int.

in addition ,T May be part of the type . for example , We use constant references to declare max()

template<typename T>
T max (T const& a, T const& b) {
    
  return b < a ? a : b;
}

We pass on two again int Give this template function ,T It is also deduced as int.

Type conversion in type derivation :

  • When invoking a parameter through a reference declaration , There will be no type conversion during type pushing , The two are declared as the same template parameters T The parameters of must match exactly . for example :
#include<iostream>

 template <typename T>
 T max(T& a, T& b) {
    
     return b < a ? a : b;
 }
 
 int main() {
    
     const int a = 3;
     int b = 4;
     std::cout << ::max(a, b) << std::endl;
     return 0;
 }

The compilation error is as follows :

hello.cpp: In function ‘int main():
hello.cpp:11:28: error: no matching function for call to ‘max(const int&, int&)11 |     std::cout << ::max(a, b) << std::endl;
      |                            ^
hello.cpp:4:3: note: candidate:template<class T> T max(T&, T&)4 | T max(T& a, T& b) {
    
      |   ^~~
hello.cpp:4:3: note:   template argument deduction/substitution failed:
hello.cpp:11:28: note:   deduced conflicting types for parameter ‘T’ (const intandint)
   11 |     std::cout << ::max(a, b) << std::endl;
      |                            ^
  • When a call parameter is declared by value , Only degradation is supported (decay) Type conversion of :cv(const and volatile) Be ignored , The reference is converted to the referenced type , Convert the original array and function to the corresponding pointer type . The two are declared as the same template parameters T The degenerate type of the parameter of must match . for example :
template<typename T>
T max (T a, T b);
...
int i = 5;
int const c = 42;
max(i, c);    // OK: T is deduced as int
max(c, c);    // OK: T is deduced as int
int& ir = i;
max(i, ir);   // OK: T is deduced as int
int arr[4];
foo(&i, arr); // OK: T is deduced as int*

But the following example has a problem :

max(4, 7.2);      // ERROR: T can be deduced as int or double
std::string s;
foo("hello", s);  // ERROR: T can be deduced as char const[6] or std::string

The solutions to the above two examples are as follows :

  1. Show cast .max(static_cast<double>(4), 7.2); // OK
  2. Show specifying template parameters to prevent compiler type derivation .max<double>(4, 7.2); // OK
  3. Use two different types of template parameters .

Function default call parameter type derivation :

Function default call parameters cannot derive type . Look at an example :

template<typename T>
void f(T = "");
...

f(1); // OK: deduced T to be int, so that it calls f<int>(1)
f();  // ERROR: cannot deduce T

To solve this problem , The default type of template parameter needs to be declared .

template<typename T = std::string>
void f(T = "");
...
f(); // OK

Multiple template parameters Multiple Template Parameters

You can specify the number of template parameters as needed . for example :

template<typename T1, typename T2>
T1 max (T1 a, T2 b) {
    
  return b < a ? a : b;
}
...
auto m = ::max(4, 7.2); // OK, but type of first argument defines return type

Grammatically , There is no problem with the above code . Just use one of the template parameters as the return type , Then change the calling order of the next two variables , You will get different results . Pass on 66.66 and 42 to max obtain double Type of 66.66, And deliver 42 and 66.66 to max obtain int Type of 66.

C++ There are different ways to solve the above problems :

  • Introduce the first 3 Template parameters as return types .
  • Let the compiler deduce the return type .
  • A public type that uses two parameters (common type) As return type .

Next, discuss the above methods respectively .

Return template parameters of value type Template Parameters for Return Types

When a template function is called , We can display the specified template parameter type , Or not ( Let the compiler deduce ). however , When there is no connection between the call parameter and the template parameter , You must display the specified template parameters when calling the template function .

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

In this case , The compiler's parameter derivation cannot handle the type of the return value ,RT Nor does it appear in the function call parameters , therefore RT Cannot be deduced . You must display the specified template parameter type :

::max<int,double,double>(4, 7.2); // OK, but tedious

Usually , You must specify all parameter types until the last parameter that cannot be implicitly derived . For this example , We can adjust the parameter position of the return value type template , In this way, we can only specify the type of return value when calling the function , Other template parameters are left to the compiler to derive .

template<typename RT, typename T1, typename T2>
RT max (T1 a, T2 b);
...
::max<double>(4, 7.2) // OK: return type is double, T1 and T2 are deduced

Derive the return value type Deducing the Return Type

If the return value type depends on the template parameter , The best way is to leave it to the compiler to deduce .C++14 You can use it directly auto As return value type .

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

The trailing return type is not used here (trailing return type) Indicates that the return value type can be derived from the return value statement in the function body . stay C++11 in , You need to use the trailing return value type declaration :

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

The type of the return value is determined by the question mark expression b<a?a:b Type to confirm . If a and b Different types of ,decltype(b<a?a:b) Will return the common types of both (common type). For the return type of question mark expression, please refer to Conditional Operator: ? : , For public types, please refer to std::common_type .

therefore , The condition of the question mark expression is not important here , You can use it directly true Instead of :

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

then , The above code has another flaw : The type of the return value may be a reference type , Because under some conditions T It could be a reference . Therefore, the following modifications are required :

#include <type_traits>
template<typename T1, typename T2>
auto max (T1 a, T2 b) -> typename std::decay<decltype(true?a:b)>::type
{
    
  return b < a ? a : b;
}

there type Is a member type , Keyword required typename To embellish .

The return type is a generic type Return Type as Common Type

C++11 The standard library provides std::common_type<>::type Produce two ( Or more ) Public type of type (common type). therefore , We can use std::common_type<>::type Specify the return value type of the template function :

#include <type_traits>
template<typename T1, typename T2>
typename std::common_type<T1,T2>::type max (T1 a, T2 b) {
    
  return b < a ? a : b;
}

C++14 To simplify the trait Library usage , Directly in trait name After add _t suffix . The above return value type can be simplified to :

std::common_type_t<T1,T2> // equivalent since C++14

Default template parameters Default Template Arguments

We can define the default values of template parameters . Take the example above , We can define max Return value type of RT, And its default value is defined as T1 and T2 Of common_type, Use the question mark expression to achieve the following :

#include <type_traits>
template<typename T1, typename T2,
typename RT = std::decay_t<decltype(true ? T1() : T2())>>
RT max (T1 a, T2 b)
{
    
  return b < a ? a : b;
}

Be careful : The above implementation , We need to be able to call the default constructor of the incoming type . namely T1 and T2 There must be a default constructor . for example :

#include <type_traits>

class A {
    
public:
    A() = default;
    A(int a): x(a) {
    }
    bool operator< (const A& a) {
    
        return this->x < a.x;
    }
private:
    int x;
};

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

int main()
{
       
    A a(2), b(3);
    auto c = ::max(a, b);
    return 0;
}

If you will A() = default; Comment out , be A There is no default constructor , The default parameter type derivation of the template fails .

main.cpp:15:44: error: no matching function for call to 'A::A()'
   15 | typename RT = std::decay_t<decltype(true ? T1() : T2())>>

You can also use std::common_type Specify the public type of the default template parameter :

#include <type_traits>
template<typename T1, typename T2,
// typename RT = typename std::common_type<T1,T2>::type> // C++11
typename RT = std::common_type_t<T1,T2>> // C++14
RT max (T1 a, T2 b)
{
    
  return b < a ? a : b;
}

We can use the default template parameter as the return value type , You can also specify the return value type :

auto a = ::max(4, 7.2);
auto b = ::max<double,int,long double>(7.2, 4);

As described earlier , We can adjust the default template parameter position to the first one , In this way, you can only specify the return value type when calling , Other template parameter types are left to the compiler to derive .

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

int i;
long l;
...
max(i, l); // returns long (default argument of template parameter for return type)
max<int>(4, 42); // returns int as explicitly requested

Function template overload Overloading Function Templates

Like a normal function , Function templates can also be overloaded . for example :

// 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()
{
    
  ::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
}

C++ Matching principle of overloaded functions : If the function instantiated by the template matches the ordinary overloaded function exactly , Ordinary overloaded functions are preferred , Secondly, select the exact version instantiated by the template function . For this, please refer to my previous blog Item 26: Avoid overloading on universal references. .

about ::max(7, 42); Both normal functions and template functions can be matched , Match ordinary functions first . about ::max(7.0, 42.0) Will match to the template function .

Of course, you can also specify an empty template parameter list , Used to indicate that the calling function template is not a normal function :

::max<>(7, 42); // calls max<int> (by argument deduction)

The parameters of ordinary functions can be automatically typed , Function templates do not . So the last one calls the normal function , Both parameters are changed to int

::max('a', 42.7); // only the nontemplate function allows nontrivial conversions

Overloaded function templates need to be guaranteed : For any call , Only one version can match , Otherwise, there will be ambiguity . for example : Overloading the return value type may cause ambiguity .

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

auto a = ::max(4, 7.2); // uses first template

however ,auto b = ::max<long double>(7.2, 4); Then both versions can match , Cause compilation error :

main.cpp:17:33: error: call of overloaded 'max<long int, double>(int, double)' is ambiguous
   17 |     auto c = ::max<long, double>(4, 7.2);
      |              ~~~~~~~~~~~~~~~~~~~^~~~~~~~
main.cpp:4:6: note: candidate: 'auto max(T1, T2) [with T1 = long int; T2 = double]'
    4 | auto max (T1 a, T2 b)
      |      ^~~
main.cpp:9:4: note: candidate: 'RT max(T1, T2) [with RT = long int; T1 = double; T2 = double]'
    9 | RT max (T1 a, T2 b)

Looking at a pointer and tradition C Examples of string overloading :

#include <cstring>
#include <string>
// maximum of two values of any type:
template<typename T>
T max (T a, T b)
{
    
	return b < a ? a : b;
}
// maximum of two pointers:
template<typename T>
T* max (T* a, T* b)
{
    
	return *b < *a ? a : b;
}
// maximum of two C-strings:
char const* max (char const* a, char const* b)
{
    
	return std::strcmp(b,a) < 0 ? a : b;
}
int main ()
{
    
	int a = 7;
	int b = 42;
	auto m1 = ::max(a,b); // max() for two values of type int
	std::string s1 = "hey";
	std::string s2 = "you";
	auto m2 = ::max(s1,s2); // max() for two values of type std::string
	int* p1 = &b;
	int* p2 = &a;
	auto m3 = ::max(p1,p2); // max() for two pointers
	char const* x = "hello";
	char const* y = "world";
	auto m4 = ::max(x,y); // max() for two C-strings
}

It is generally recommended to use function templates that pass values , If the template function passed to the reference is implemented , Reload the transmission C String value version , Can lead to mistakes :

#include <cstring>
// 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 ()
{
    
	char const* s1 = "frederic";
	char const* s2 = "anica";
	char const* s3 = "lucas";
	auto m2 = ::max(s1, s2, s3); // run-time ERROR
}

max(max(a, b), c) in ,max(a, b) A reference to a temporary object is generated , This temporary reference is in return max (max(a,b), c); The end of the statement is invalid , Causes the reference to hang :

main.cpp: In instantiation of 'const T& max(const T&, const T&, const T&) [with T = const char*]':
main.cpp:24:17:   required from here
main.cpp:17:20: warning: returning reference to temporary [-Wreturn-local-addr]
   17 |         return max (max(a,b), c); // error if max(a,b) uses call-by-value
      |                ~~~~^~~~~~~~~~~~~
bash: line 7:  2907 Segmentation fault      (core dumped) ./a.out

Besides , The overloaded version needs to be declared before calling , Otherwise, it may produce unexpected results . for example :

#include <iostream>
// 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); // uses the template version even for ints
}                             // because the following declaration comes
                              // too late:
// maximum of two int values:
int max (int a, int b)
{
    
std::cout << "max(int,int) \n";
	return b < a ? a : b;
}
int main()
{
    
	::max(47,11,33); // OOPS: uses max<T>() instead of max(int,int)
}

Besides ,C++11 have access to constexpr Function to generate compile time values .

template <typename T, typename U>
constexpr auto max(T a, U b) {
    
  return b < a ? a : b;
}

int a[::max(sizeof(char), 1000u)];
std::array<int, ::max(sizeof(char), 1000u)> b;

summary Summary

  • Function templates define a family of functions
  • The compiler can derive template parameters from the passed parameters
  • You can also display the specified template parameters
  • You can define default template parameters
  • Function templates can be overloaded
  • When reloading a template function , You need to ensure that there is only one matching version for any call
  • When reloading a function template , Pay attention to the reference suspension caused by reference passing and value passing versions
  • The overloaded version needs to be declared before calling

Reference resources Reference

  • http://www.tmplbook.com
  • https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2012/e4213hs1%28v=vs.110%29?redirectedfrom=MSDN
  • https://en.cppreference.com/w/cpp/types/common_type
原网站

版权声明
本文为[loongknown]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/163/202206120221391164.html