Skip to content

Latest commit

 

History

History
454 lines (334 loc) · 18.2 KB

19.2 — Function template instances.md

File metadata and controls

454 lines (334 loc) · 18.2 KB

19.2 — Function template instances

It’s worth taking a brief look at how template functions are implemented in C++, because future lessons will build off of some of these concepts. It turns out that C++ does not compile the template function directly. Instead, at compile time, when the compiler encounters a call to a template function, it replicates the template function and replaces the template type parameters with actual types. The function with actual types is called a function template instance.

值得简单看一下模板函数在C++里面是怎么实现的。因为将来的课程是基于这其中的一些概念的。C++不会直接编译这些模板函数。在编译的时候,当编译器遇到一个模板函数的调用,它会复制这个模板函数,然后把实际的类型填进去。带有实际类型的函数叫做函数模板实例。

Let’s take a look at an example of this process. First, we have a templated function:

我们探究一下:

template <typename T> // this is the template parameter declaration
const T& max(const T& x, const T& y)
{
    return (x > y) ? x : y;
}

When compiling your program, the compiler encounters a call to the templated function:

当编译你的程序的时候,编译器遇到对模板函数的调用的时候

int i{ max(3, 7) }; // calls max(int, int)

The compiler says, “oh, we want to call max(int, int)”. The compiler replicates the function template and creates the template instance max(int, int):

编译器说,哦,我们要调用max(int, int),编译器复制了一份模板函数,然后创建了函数模板实例max(int, int)

const int& max(const int &x, const int &y)
{
    return (x > y) ? x : y;
}

This is now a “normal function” that can be compiled into machine language.

现在这个东西就变成了一个可以编译的普通函数了。

Now, let’s say later in your code, you called max() again using a different type:

现在,假设你在之后的代码里面又调用了max,但这次的类型又不一样了。

double d{ max(6.34, 18.523) }; // calls max(double, double)

C++ automatically creates a template instance for max(double, double):

C++又自动的创建了一份max(double, double)的函数模板实例。

const double& max(const double &x, const double &y)
{
    return (x > y) ? x : y;
}

and then compiles it.

然后编译这个函数模板实例。

The compiler is smart enough to know it only needs to create one template instance per set of unique type parameters (per file). It’s also worth noting that if you create a template function but do not call it, no template instances will be created.

编译器足够聪明的知道每个独特的类型每个文件,他只需要创建一份模板实例。更值得注意的是,如果你创建了一个模板函数,但是你没有调用,那么就没有模板实例会被创建。

Operators, function calls, and function templates

Template functions will work with both built-in types (e.g. char, int, double, etc…) and classes, with one caveat. When the compiler compiles the template instance, it compiles it just like a normal function. In a normal function, any operators or function calls that you use with your types must be defined, or you will get a compiler error. Similarly, any operators or function calls in your template function must be defined for any types the function template is instantiated for. Let’s take a look at this in more detail.

模板函数可以对内建的数据类型和class起作用。但是有一个警告,当编译器编译一个模板实例的时候,他就像对普通的函数一样去编译他。在普通的函数中,任何使用的运算符或者是函数,都必须被定义,否则你就会得到一个编译错误。相似的,你在模板函数中使用的运算符和函数也都必须被定义。

First, we’ll create a simple class:

class Cents
{
private:
    int m_cents;
public:
    Cents(int cents)
        : m_cents{ cents }
    {
    }
};

Now, let’s see what happens when we try to call our templated max() function with the Cents class:

现在看一下如果我们对我们的Cent类调用max函数的话会怎么样。

template <typename T> // this is the template parameter declaration
const T& max(const T& x, const T& y)
{
    return (x > y) ? x : y;
}
 
class Cents
{
private:
    int m_cents;
public:
    Cents(int cents)
        : m_cents{ cents }
    {
    }
};
 
int main()
{
    Cents nickle{ 5 };
    Cents dime{ 10 };
 
    Cents bigger{ max(nickle, dime) };
 
    return 0;
}

C++ will create a template instance for max() that looks like this:

C++会创建一个函数模板实例:

const Cents& max(const Cents &x, const Cents &y)
{
    return (x > y) ? x : y;
}

And then it will try to compile this function. See the problem here? C++ can’t evaluate x > y, because x and y are Cents class objects, and doesn’t know how to compare them. Consequently, this will produce a fairly-tame looking compile error, like this:

然后它会试图编译这个函数,看到问题了吗?C++不能对x>y进行估值,因为x和y是Cent类的对象,不知道怎么去比较他们。因此它会产生一个编译错误。

1>c:\consoleapplication1\main.cpp(4): error C2676: binary '>': 'const Cents' does not define this operator or a conversion to a type acceptable to the predefined operator
1>  c:\consoleapplication1\main.cpp(23): note: see reference to function template instantiation 'const T &max(const T &,const T &)' being compiled
1>          with
1>          [
1>              T=Cents
1>          ]

The top error message points out the fact that there is no overloaded operator > for the Cents class. The bottom error points out the templated function call that spawned the error, along with the type of the templated parameter.

顶部的消息说明没有对>号的运算符重载函数。下面的错误指明是哪个模板函数调用产生了这个错误,伴随有具体的函数模板类型参数。

To get around this problem, simply overload the > operator for any class we wish to use max() with:

要解决这个问题的话,只需要简单的重载对我们想要操作的类实现这个>运算符就可以了。

class Cents
{
private:
    int m_cents;
public:
    Cents(int cents)
        : m_cents{ cents }
    {
    }
 
    friend bool operator>(const Cents &c1, const Cents &c2)
    {
        return (c1.m_cents > c2.m_cents);
    }
};

Now C++ will know how to compare x > y when x and y are objects of the Cents class! As a result, our max() function will now work with two objects of type Cents.

现在C++知道该怎么做了。

Another example

Let’s do one more example of a function template. The following function template will calculate the average of a number of objects in an array:

我们再看一个函数模板的例子,下面的函数模板会计算数组中元素的平均值

template <class T>
T average(T *array, int length)
{
    T sum(0);
 
    for (int count{ 0 }; count < length; ++count)
        sum += array[count];
 
    sum /= length;
    return sum;
}

Now let’s see it in action:

#include <iostream>
 
template <class T>
T average(T *array, int length)
{
    T sum(0);
    for (int count{ 0 }; count < length; ++count)
        sum += array[count];
 
    sum /= length;
    return sum;
}
 
int main()
{
    int array1[]{ 5, 3, 2, 1, 4 };
    std::cout << average(array1, 5) << '\n';
 
    double array2[]{ 3.12, 3.45, 9.23, 6.34 };
    std::cout << average(array2, 4) << '\n';
 
    return 0;
}

This produces the values:

3
5.535

As you can see, it works great for built-in types!

它对内建的数据类型没有问题

It is worth noting that because our return type is the same templated type as our array elements, doing an integer average will produce an integer result (dropping any fractional value). This is similar to how doing an integer division will produce an integer result. It’s not wrong that we’ve defined things to work that way, but it may be unexpected, so a good comment to users of the class wouldn’t be amiss here.

值得注意的是,我们的返回类型就是函数模板的类型参数,做一个整型数组的平均值也会产生一个整型值,把小数部分都扔掉了。这跟对整数的运算总会产生整数很相似。在我们故意那样定义那样的行为的时候,没什么错。但是它可能是不想要的。所以这么干,这个函数的使用者不会给好评。

Now let’s see what happens when we call this function on our Cents class:

我们看一下当我们把这个函数应用在我们的Cent类上的时候会发生什么。

#include <iostream>
 
class Cents
{
private:
    int m_cents;
public:
    Cents(int cents)
        : m_cents{ cents }
    {
    }
 
    friend bool operator>(const Cents &c1, const Cents &c2)
    {
        return (c1.m_cents > c2.m_cents);
    }
};
 
template <class T>
T average(T *array, int length)
{
    T sum(0);
    for (int count{ 0 }; count < length; ++count)
        sum += array[count];
 
    sum /= length;
    return sum;
}
 
int main()
{
    Cents array3[]{ Cents(5), Cents(10), Cents(15), Cents(14) };
    std::cout << average(array3, 4) << '\n';
 
    return 0;
}

The compiler goes berserk and produces a ton of error messages!

编译器报告了一堆的错误信息。

example.cpp

(33): error C2679: binary '<<': no operator found which takes a right-hand operand of type 'T' (or there is no acceptable conversion)

        with

        [

            T=Cents

        ]

C:/data/msvc/14.22.27905/include\ostream(437): note: could be 'std::basic_ostream> &std::basic_ostream>::operator <<(std::basic_streambuf> *)'
C:/data/msvc/14.22.27905/include\ostream(412): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(const void *)'
C:/data/msvc/14.22.27905/include\ostream(394): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(long double)'
C:/data/msvc/14.22.27905/include\ostream(376): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(double)'
C:/data/msvc/14.22.27905/include\ostream(358): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(float)'
C:/data/msvc/14.22.27905/include\ostream(340): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(unsigned __int64)'
C:/data/msvc/14.22.27905/include\ostream(322): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(__int64)'
C:/data/msvc/14.22.27905/include\ostream(304): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(unsigned long)'
C:/data/msvc/14.22.27905/include\ostream(286): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(long)'
C:/data/msvc/14.22.27905/include\ostream(268): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(unsigned int)'
C:/data/msvc/14.22.27905/include\ostream(248): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(int)'
C:/data/msvc/14.22.27905/include\ostream(230): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(unsigned short)'
C:/data/msvc/14.22.27905/include\ostream(202): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(short)'
C:/data/msvc/14.22.27905/include\ostream(184): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(bool)'
C:/data/msvc/14.22.27905/include\ostream(179): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(std::ios_base &(__cdecl *)(std::ios_base &))'
C:/data/msvc/14.22.27905/include\ostream(174): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(std::basic_ios> &(__cdecl *)(std::basic_ios> &))'
C:/data/msvc/14.22.27905/include\ostream(169): note: or       'std::basic_ostream> &std::basic_ostream>::operator <<(std::basic_ostream> &(__cdecl *)(std::basic_ostream> &))'
C:/data/msvc/14.22.27905/include\ostream(613): note: or       'std::basic_ostream> &std::operator <<>(std::basic_ostream> &,const char *)'
C:/data/msvc/14.22.27905/include\ostream(658): note: or       'std::basic_ostream> &std::operator <<>(std::basic_ostream> &,char)'
C:/data/msvc/14.22.27905/include\ostream(694): note: or       'std::basic_ostream> &std::operator <<>(std::basic_ostream> &,const char *)'
C:/data/msvc/14.22.27905/include\ostream(739): note: or       'std::basic_ostream> &std::operator <<>(std::basic_ostream> &,char)'
C:/data/msvc/14.22.27905/include\ostream(858): note: or       'std::basic_ostream> &std::operator <<>(std::basic_ostream> &,const signed char *)'
C:/data/msvc/14.22.27905/include\ostream(864): note: or       'std::basic_ostream> &std::operator <<>(std::basic_ostream> &,signed char)'
C:/data/msvc/14.22.27905/include\ostream(870): note: or       'std::basic_ostream> &std::operator <<>(std::basic_ostream> &,const unsigned char *)'
C:/data/msvc/14.22.27905/include\ostream(876): note: or       'std::basic_ostream> &std::operator <<>(std::basic_ostream> &,unsigned char)'
C:/data/msvc/14.22.27905/include\ostream(931): note: or       'std::basic_ostream> &std::operator <<>(std::basic_ostream> &,const std::error_code &)'
(33): note: while trying to match the argument list '(std::ostream, T)'

        with

        [

            T=Cents

        ]

Compiler returned: 2

Remember what I said about crazy error messages? We hit the motherlode! Despite looking intimidating, these are actually quite straightforward. The first line is telling you that it couldn’t find an overloaded operator<< for the Cents class. All of the lines in the middle are all of the different functions it tried to match with but failed. The last error points out the function call that spawned this wall of errors.

还记得我说过疯了的错误消息吗?我们碰到G点了。不管这看起来有多让人望而生畏,它实际上相当简单直白。第一行告诉你他找不到Cent类对应的<<运算符重载函数,中间所有的行都是不同的函数试图去匹配的时候失败了。

Remember that average() returns a Cents object, and we are trying to stream that object to std::cout using the << operator. However, we haven’t defined the << operator for our Cents class yet. Let’s do that:

记住average返回一个Cent对象,我试图把那个对象通过流输出运算符,输出到cout。然而我们没有定义流输出运算符重载函数。

class Cents
{
private:
    int m_cents;
public:
    Cents(int cents)
        : m_cents{ cents }
    {
    }
 
    friend bool operator>(const Cents &c1, const Cents &c2)
    {
        return (c1.m_cents > c2.m_cents);
    }
 
    friend std::ostream& operator<< (std::ostream &out, const Cents &cents)
    {
        out << cents.m_cents << " cents ";
        return out;
    }
};

If we compile again, we will get another error:

如果我们现在编译的话,我们又得到了一个错误。

c:test.cpp(14) : error C2676: binary '+=' : 'Cents' does not define this operator or a conversion to a type acceptable to the predefined operator

This error is actually being caused by the function template instance created when we call average(Cents*, int). Remember that when we call a templated function, the compiler “stencils” out a copy of the function where the template type parameters (the placeholder types) have been replaced with the actual types in the function call. Here is the function template instance for average() when T is a Cents object:

这个错误实际上是由函数模板实例导致的。记住当我调用模板函数的时候,编译器印刻出一份模板函数的拷贝,而这份拷贝的类型参数已经被实际的类型填好。下面是函数模板实例的样子

template <class T>
Cents average(Cents *array, int length)
{
    Cents sum(0);
    for (int count{ 0 }; count < length; ++count)
        sum += array[count];
 
    sum /= length;
    return sum;
}

The reason we are getting an error message is because of the following line:

错误的原因在这里

      sum += array[count];

In this case, sum is a Cents object, but we have not defined the += operator for Cents objects! We will need to define this function in order for average() to be able to work with Cents. Looking forward, we can see that average() also uses the /= operator, so we will go ahead and define that as well:

在这个例子中,sum是一个Cent对象,但是我们还没有定义这个+=操作符。我们需要定义这个函数来似的average函数能够与Cent工作,再往前看,我们可以看到average也使用了/=运算符,我们也需要定义这个运算符。

class Cents
{
private:
    int m_cents;
public:
    Cents(int cents)
        : m_cents{ cents }
    {
    }
 
    friend bool operator>(const Cents &c1, const Cents &c2)
    {
        return (c1.m_cents > c2.m_cents);
    }
 
    friend std::ostream& operator<< (std::ostream &out, const Cents &cents)
    {
        out << cents.m_cents << " cents ";
        return out;
    }
 
    Cents& operator+=(const Cents &cents)
    {
        m_cents += cents.m_cents;
        return *this;
    }
 
    Cents& operator/=(int value)
    {
        m_cents /= value;
        return *this;
    }
};

Finally, our code will compile and run! Here is the result:

最后我们的代码就可以通过编译运行了。

11 cents

If this seems like a lot of work, that’s really only because our Cents class was so bare-bones to start. The key point here is actually that we didn’t have to modify average() at all to make it work with objects of type Cents (or any other type). We simply had to define the operators used to implement average() for the Cents class, and the compiler took care of the rest!

如果这看起来需要做很多的工作,这只是我们的Cents类实在是太简单了,要的东西都没有。这里的关键点在于,我们不需要改动average这个模板函数。我们只需要定义average要用到的运算符就可以。编译器会做剩下的事情。