Skip to content

Latest commit

 

History

History
563 lines (407 loc) · 17.1 KB

19.7 — Partial template specialization.md

File metadata and controls

563 lines (407 loc) · 17.1 KB

19.7 — Partial template specialization

This lesson and the next are optional reading for those desiring a deeper knowledge of C++ templates. Partial template specialization is not used all that often (but can be useful in specific cases).

这一节和下一节是选读内容。部分特例化并不是很常用,但是在特定情境下很有用。

In lesson 19.4 -- Template non-type parameters, you learned how expression parameters could be used to parameterize template classes.

在前面你已经学了模板的参数不一定要是类型参数。

Let’s take another look at the Static Array class we used in one of our previous examples:

我们再看一下之前写的StaticArray类

template <class T, int size> // size is the expression parameter
class StaticArray
{
private:
    // The expression parameter controls the size of the array
    T m_array[size]{};
 
public:
    T* getArray() { return m_array; }
	
    T& operator[](int index)
    {
        return m_array[index];
    }
};

This class takes two template parameters, a type parameter, and an expression parameter.

这个类接受两个模板参数,一个是模板类型参数,另一个是表达式参数

Now, let’s say we wanted to write a function to print out the whole array. Although we could implement this as a member function, we’re going to do it as a non-member function instead because it will make the successive examples easier to follow.

现在假设我们想写一个函数打印出来整个数组的元素,尽管我们可以把它实现成一个成员函数,但是我们现在要把它写成一个非成员函数,这是为了教学设计。

Using templates, we might write something like this:

用模板的话,我们就写出来这样的东西:

template <typename T, int size>
void print(StaticArray<T, size> &array)
{
    for (int count{ 0 }; count < size; ++count)
        std::cout << array[count] << ' ';
}

This would allow us to do the following:

这使得我们可以作下面的事情

#include <iostream>
#include <cstring>
 
template <class T, int size> // size is the expression parameter
class StaticArray
{
private:
	// The expression parameter controls the size of the array
	T m_array[size]{};
 
public:
	T* getArray() { return m_array; }
 
	T& operator[](int index)
	{
		return m_array[index];
	}
};
 
template <typename T, int size>
void print(StaticArray<T, size> &array)
{
	for (int count{ 0 }; count < size; ++count)
		std::cout << array[count] << ' ';
}
 
int main()
{
	// declare an int array
	StaticArray<int, 4> int4{};
	int4[0] = 0;
	int4[1] = 1;
	int4[2] = 2;
	int4[3] = 3;
 
	// Print the array
	print(int4);
 
	return 0;
}

and get the following result:

0 1 2 3

Although this works, it has a design flaw. Consider the following:

尽管答案正确,但是这里有一个设计问题,思考一下下面的例子:

int main()
{
    // declare a char array
    StaticArray<char, 14> char14{};
 
    std::strcpy(char14.getArray(), "Hello, world!");
 
    // Print the array
    print(char14);
 
    return 0;
}

(We covered std::strcpy in lesson 9.6 -- C-style strings if you need a refresher)

This program will compile, execute, and produce the following value (or one similar):

这个程序能编译,运行,产生下面的结果

H e l l o ,   w o r l d !

For non-char types, it makes sense to put a space between each array element, so they don’t run together. However, with a char type, it makes more sense to print everything run together as a C-style string, which our print() function doesn’t do.

So how can we fix this?

对于不是char的类型,中间放一个空格是有意义的,但是对于char类型来说,输出的时候中间夹一个空格就不好了。我们怎么修复这个问题?

Template specialization to the rescue? 模板特例化能解决问题吗?

One might first think of using template specialization. The problem with full template specialization is that all template parameters must be explicitly defined.

我们可能想到的一个解决办法就是用钱买的模板特例化。但是模板特例化的问题在于所有的模板参数必须被已经定义。

Consider:

#include <iostream>
#include <cstring>
 
template <class T, int size> // size is the expression parameter
class StaticArray
{
private:
	// The expression parameter controls the size of the array
	T m_array[size]{};
 
public:
	T* getArray() { return m_array; }
 
	T& operator[](int index)
	{
		return m_array[index];
	}
};
 
template <typename T, int size>
void print(StaticArray<T, size> &array)
{
	for (int count{ 0 }; count < size; ++count)
		std::cout << array[count] << ' ';
}
 
// Override print() for fully specialized StaticArray<char, 14>
template <>
void print(StaticArray<char, 14> &array)
{
	for (int count{ 0 }; count < 14; ++count)
		std::cout << array[count];
}
 
int main()
{
    // declare a char array
    StaticArray<char, 14> char14{};
 
    std::strcpy(char14.getArray(), "Hello, world!");
 
    // Print the array
    print(char14);
 
    return 0;
}

As you can see, we’ve now provided an overloaded print function for fully specialized StaticArray<char, 14>. Indeed, this prints:

正如你所看到的,我们现在提供了一个重载的print函数给这个特化的StaticArray,确实,它也打印出来了:

Hello, world!

Although this solves the issue of making sure print() can be called with a StaticArray<char, 14>, it brings up another problem: using full template specialization means we have to explicitly define the length of the array this function will accept! Consider the following example:

尽管这个解决了print的问题,但是它也代来了另一个问题。使用完全特例化的模板特例化意味着我们必须清晰的定义函数愿意接受的数组的长度。思考一下下面的例子:

int main()
{
    // declare a char array
    StaticArray<char, 12> char12{};
 
    std::strcpy(char12.getArray(), "Hello, mom!");
 
    // Print the array
    print(char12);
 
    return 0;
}

Calling print() with char12 will call the version of print() that takes a StaticArray<T, size>, because char12 is of type StaticArray<char, 12>, and our overloaded print() will only be called when passed a StaticArray<char, 14>.

调用print的时候把char12传入会导致类型不匹配的问题,因为形参是StaticArray<char, 14>,而实际参数是StaticArray<char, 12>。(完全模板特例化导致没办法调整特例化的非类型模板参数,灵活性有问题

Although we could make a copy of print() that handles StaticArray<char, 12>, what happens when we want to call print() with an array size of 5, or 22? We’d have to copy the function for each different array size. That’s redundant.

尽管我们可以搞一份针对12的拷贝,但这毕竟不是办法。

Obviously full template specialization is too restrictive a solution here. The solution we are looking for is partial template specialization.

显然,完整的函数模板特例化是一种带有限制的解决方案,解决办法是我们要用部分模板特例化。(完全模板特例化在面对带有非类型模板参数的模板时,有非类型模板参数得写定固定值的限制)

Partial template specialization 部分模板特例化

Partial template specialization allows us to specialize classes (but not individual functions!) where some, but not all, of the template parameters have been explicitly defined. For our challenge above, the ideal solution would be to have our overloaded print function work with StaticArray of type char, but leave the length expression parameter templated so it can vary as needed. Partial template specialization allows us to do just that!

部分模板特例化允许我们部分地特例化类(但对单独的函数不行)的时候,特例化一些,不特例化一些(在下面的例子中特例化了类型为char,没有特例化size为具体的值)。对于我们上面的挑战,解决办法是写一个我们的重载函数能接受char类型的StaticArray,但是让这个数组的大小保持可以按需设置的特性,部分模板特例化允许我们这样干。

Here’s our example with an overloaded print function that takes a partially specialized StaticArray:

下面使我们部分特例化了的print函数。

// overload of print() function for partially specialized StaticArray<char, size>
template <int size> // size is still a templated expression parameter
void print(StaticArray<char, size> &array) // we're explicitly defining type char here
{
	for (int count{ 0 }; count < size; ++count)
		std::cout << array[count];
}

As you can see here, we’ve explicitly declared that this function will only work for StaticArray of type char, but size is still a templated expression parameter, so it will work for char arrays of any size. That’s all there is to it!

正如你看到的那样,我们已经明确声明了这个函数只对char类型的StaticArray起作用,但是size仍然是一个模板参数,所以他能对任何大小的char数组起作用。这就是全部了!

Here’s a full program using this:

#include <iostream>
#include <cstring>
 
template <class T, int size> // size is the expression parameter
class StaticArray
{
private:
	// The expression parameter controls the size of the array
	T m_array[size]{};
 
public:
	T* getArray() { return m_array; }
 
	T& operator[](int index)
	{
		return m_array[index];
	}
};
 
template <typename T, int size>
void print(StaticArray<T, size> &array)
{
	for (int count{ 0 }; count < size; ++count)
		std::cout << array[count] << ' ';
}
 
// overload of print() function for partially specialized StaticArray<char, size>
template <int size>
void print(StaticArray<char, size> &array)
{
	for (int count{ 0 }; count < size; ++count)
		std::cout << array[count];
}
 
int main()
{
	// Declare an char array of size 14
	StaticArray<char, 14> char14{};
 
	std::strcpy(char14.getArray(), "Hello, world!");
 
	// Print the array
	print(char14);
 
	// Now declare an char array of size 12
	StaticArray<char, 12> char12{};
 
	std::strcpy(char12.getArray(), "Hello, mom!");
 
	// Print the array
	print(char12);
 
	return 0;
}

This prints:

Hello, world! Hello, mom!

Just as we expect.

跟我们想的一样

Note that as of C++14, partial template specialization can only be used with classes, not template functions (functions must be fully specialized). Our void print(StaticArray<char, size> &array) example works because the print function is not partially specialized (it’s just an overloaded function using a class parameter that’s partially specialized).

请注意从C++14开始,部分模板特例化只能被作用于模板类类,而不能作用于模板函数(函数必须被完全特例化)。我们的这个例子管用是因为print函数不是部分特例化(它只是对部分特例化的模板类对象的引用作为参数的重载)

Partial template specialization for member functions 对成员函数的部分模板特例化

The limitation on the partial specialization of functions can lead to some challenges when dealing with member functions. For example, what if we had defined StaticArray like this?

在我们处理成员函数的时候,对函数的模板部分模板特例化的限制会导致一些麻烦。举个例子,如果我们这样定义StaticArray的话:

template <class T, int size> // size is the expression parameter
class StaticArray
{
private:
    // The expression parameter controls the size of the array
    T m_array[size]{};
 
public:
    T* getArray() { return m_array; }
	
    T& operator[](int index)
    {
        return m_array[index];
    }
 
    void print()
    {
        for (int i{ 0 }; i < size; ++i)
            std::cout << m_array[i] << ' ';
        std::cout << '\n';
    }
};

print() is now a member function of class StaticArray<T, int>. So what happens when we want to partially specialize print(), so that it works differently? You might try this:

print现在变成了成员函数,所以当我们试图部分地特例化print的时候会怎么样?你可能会这么做:

// Doesn't work
template <int size>
void StaticArray<double, size>::print()
{
	for (int i{ 0 }; i < size; ++i)
		std::cout << std::scientific << m_array[i] << ' ';
	std::cout << '\n';
}

Unfortunately, this doesn’t work, because we’re trying to partially specialize a function, which is disallowed.

不幸的是,这样不行。因为我们正在试图特例化一个函数,这不被允许。

So how do we get around this? One obvious way is to partially specialize the entire class:

所以我们怎么办呢?一个明显的办法是部分特例化整个类。

#include<iostream>
 
template <class T, int size> // size is the expression parameter
class StaticArray
{
private:
	// The expression parameter controls the size of the array
	T m_array[size]{};
 
public:
	T* getArray() { return m_array; }
 
	T& operator[](int index)
	{
		return m_array[index];
	}
	void print()
	{
		for (int i{ 0 }; i < size; ++i)
			std::cout << m_array[i] << ' ';
		std::cout << "\n";
	}
};
 
template <int size> // size is the expression parameter
class StaticArray<double, size>
{
private:
	// The expression parameter controls the size of the array
	double m_array[size]{};
 
public:
	double* getArray() { return m_array; }
 
	double& operator[](int index)
	{
		return m_array[index];
	}
	void print()
	{
		for (int i{ 0 }; i < size; ++i)
			std::cout << std::scientific << m_array[i] << ' ';
		std::cout << '\n';
	}
};
 
int main()
{
	// declare an integer array with room for 6 integers
	StaticArray<int, 6> intArray{};
 
	// Fill it up in order, then print it
	for (int count{ 0 }; count < 6; ++count)
		intArray[count] = count;
 
	intArray.print();
 
	// declare a double buffer with room for 4 doubles
	StaticArray<double, 4> doubleArray{};
 
	for (int count{ 0 }; count < 4; ++count)
		doubleArray[count] = (4.0 + 0.1 * count);
 
	doubleArray.print();
 
	return 0;
}

This prints:

0 1 2 3 4 5
4.000000e+00 4.100000e+00 4.200000e+00 4.300000e+00

While it works, this isn’t a great solution, because we had to duplicate a lot of code from StaticArray<T, size> to StaticArray<double, size>.

If only there were some way to reuse the code in StaticArray<T, size> in StaticArray<double, size>. Sounds like a job for inheritance!

管用,但不是好方法,我们造成了大量的重复代码。要是我们可以重用StaticArray<T, size> 的代码就好了,听起来像是一个继承的事情???

You might start off trying to write that code like this:

你可能开始这么写代码

template <int size> // size is the expression parameter
class StaticArray<double, size>: public StaticArray< // Then what?

template // size is the expression parameter

size是模板参数

class StaticArray<double, size>: public StaticArray< // Then what?

写了这些之后,再怎么写呢?

#include<iostream>
 
template <class T, int size> // size is the expression parameter
class StaticArray_Base
{
protected:
	// The expression parameter controls the size of the array
	T m_array[size]{};
 
public:
	T* getArray() { return m_array; }
 
	T& operator[](int index)
	{
		return m_array[index];
	}
 
	void print()
	{
		for (int i{ 0 }; i < size; ++i)
			std::cout << m_array[i];
		std::cout << '\n';
	}
 
	virtual ~StaticArray_Base() = default;
};
 
template <class T, int size> // size is the expression parameter
class StaticArray: public StaticArray_Base<T, size>
{
public:
};
 
template <int size> // size is the expression parameter
class StaticArray<double, size>: public StaticArray_Base<double, size>
{
public:
 
	void print()
	{
		for (int i{ 0 }; i < size; ++i)
			std::cout << std::scientific << this->m_array[i] << ' ';
// note: The this-> prefix in the above line is needed.
// See https://stackoverflow.com/a/6592617 or https://isocpp.org/wiki/faq/templates#nondependent-name-lookup-members for more info on why.
		std::cout << '\n';
	}
};
 
int main()
{
	// declare an integer array with room for 6 integers
	StaticArray<int, 6> intArray{};
 
	// Fill it up in order, then print it
	for (int count{ 0 }; count < 6; ++count)
		intArray[count] = count;
 
	intArray.print();
 
	// declare a double buffer with room for 4 doubles
	StaticArray<double, 4> doubleArray{};
 
	for (int count{ 0 }; count < 4; ++count)
		doubleArray[count] = (4.0 + 0.1 * count);
 
	doubleArray.print();
 
	return 0;
}

This prints the same as above, but has significantly less duplicated code.

这个结果和上面的一样,但是极大的减少了重复代码。