C++学习笔记----11、模块、头文件及各种主题(二)---- 函数模板(2)
3、函数模板作为类模板的朋友
在类模板中,当想要重载操作符时,函数模板是有用的。例如,可能想要重载Grid类模板的加操作符(operator+)以便能够使两个Grid相加。结果是与两个操作数中最小的Grid相同大小的Grid。只有两个网格都含有一个真实的值时,对应的网格才会相加在一起。假设想使operator+成为一个单独的函数模板。其定义会在Grid.cppm模块接口文件中,看起来如下。实现使用了std::min(),定义在<algorithm>,返回两个给定值中较小的那个。
export
template <typename T>
Grid<T> operator+(const Grid<T>& lhs, const Grid<T>& rhs)
{std::size_t minWidth{ std::min(lhs.m_width, rhs.m_width) };std::size_t minHeight{ std::min(lhs.m_height, rhs.m_height) };Grid<T> result{ minWidth, minHeight };for (std::size_t y{ 0 }; y < minHeight; ++y) {for (std::size_t x{ 0 }; x < minWidth; ++x) {const auto& leftElement{ lhs.at(x, y) };const auto& rightElement{ rhs.at(x, y) };if (leftElement.has_value() && rightElement.has_value()) {result.at(x, y) = leftElement.value() + rightElement.value();}}}return result;
}
为了查询optional是否包含一个真实的值,使用has_value()成员函数,而value()用于查询该值。
该函数模板作用于任何Grid,只要保存在grid中的元素类型有加操作符。该实现唯一的问题是它访问Grid类的私有成员m_width与m_height。很明显的解决方案是使用公共的getWidth()与getHeight()成员函数,但是让我们看一个如何能够使函数模板成为类模板的朋友。在这个例子中,可以使操作符成为Grid类模板的朋友。然而,Grid与operator+都是模板。你其实想要的是每个特别的类型T的operator+的每个实例都成为Grid模板相同类型的实例的一个朋友。语法看起来像这样:
export template <typename T>
class Grid
{
public:friend Grid operator+<T>(const Grid& lhs, const Grid& rhs);// Omitted for brevity
};
该模板朋友的声明有点搞:你要说的是,对于类型T的类模板的实例,operator+的T的实例化是一个朋友。换句话说,在类实例与函数实例之间有一对一的朋友影射。需要特别注意的是,在operator+上的<T>的显示规格。该语法告诉编译器operator+自身是一个模板。
该朋友operator+可以测试如下。如下的代码首先定义了两个辅助函数模板:fillGrid(),它用递增的数字填充了Grid,printGrid(),它打印任何Grid到控制台。
import grid;
import std;using namespace std;template<typename T>
void fillGrid(Grid<T>& grid)
{T index{ 0 };for (size_t y{ 0 }; y < grid.getHeight(); ++y) {for (size_t x{ 0 }; x < grid.getWidth(); ++x) {grid.at(x, y) = ++index;}}
}template<typename T>
void printGrid(const Grid<T>& grid)
{for (size_t y{ 0 }; y < grid.getHeight(); ++y) {for (size_t x{ 0 }; x < grid.getWidth(); ++x) {const auto& element{ grid.at(x, y) };if (element.has_value()) {print("{}\t", element.value());} else {print("n/a\t");}}println("");}
}int main()
{Grid<int> grid1{ 2, 2 };Grid<int> grid2{ 3, 3 };fillGrid(grid1);println("grid1:");printGrid(grid1);fillGrid(grid2);println("\ngrid2:");printGrid(grid2);auto result{ grid1 + grid2 };println("\ngrid1 + grid2:");printGrid(result);
}
4、模板类型参数推断的更多内容
编译器基于传递给函数模板的参数来推断函数模板参数的类型。无法推断出的模板参数需要显示指定。
例如,下面的add()函数模板要求三个模板参数:返回值的类型与两个操作数的类型:
template <typename RetType, typename T1, typename T2>
RetType add(const T1& t1, const T2& t2) { return t1 + t2; }
可以指定三个参数来调用该函数模板如下:
auto result { add<long long, int, int>(1, 2) };
然而,因为模板参数T1与T2是函数的参数,编译器可以推断出这两个参数,所以可以只指定返回值的类型来调用add():
auto result { add<long long>(1, 2) };
只有在推断参数为参数列表中排在最后时才有效。假定函数模板定义如下:
template <typename T1, typename RetType, typename T2>
RetType add(const T1& t1, const T2& t2) { return t1 + t2; }
就需要指定RetType,因为编译器无法推断出其类型。然而,因为RetType是第二个参数,所以也必须显示指定T1:
auto result { add<int, long long>(1, 2) };
也可以为返回类型模板参数提供一个缺省值,这样的话就可以不指定任何类型来调用add():
template <typename RetType = long long, typename T1, typename T2>
RetType add(const T1& t1, const T2& t2) { return t1 + t2; }
...
auto result { add(1, 2) };