C++学习笔记----11、模块、头文件及各种主题(二)---- 函数模板(2)

3、函数模板作为类模板的朋友

在类模板中,当想要重载操作符时,函数模板是有用的。例如,可能想要重载Grid类模板的加操作符(operator+)以便能够使两个Grid相加。结果是与两个操作数中最小的Grid相同大小的Grid。只有两个网格都含有一个真实的值时,对应的网格才会相加在一起。假设想使operator+成为一个单独的函数模板。其定义会在Grid.cppm模块接口文件中,看起来如下。实现使用了std::min(),定义在<algorithm>,返回两个给定值中较小的那个。

cpp 复制代码
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模板相同类型的实例的一个朋友。语法看起来像这样:

cpp 复制代码
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到控制台。

cpp 复制代码
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()函数模板要求三个模板参数:返回值的类型与两个操作数的类型:

cpp 复制代码
template <typename RetType, typename T1, typename T2>
RetType add(const T1& t1, const T2& t2) { return t1 + t2; }

可以指定三个参数来调用该函数模板如下:

cpp 复制代码
auto result { add<long long, int, int>(1, 2) };

然而,因为模板参数T1与T2是函数的参数,编译器可以推断出这两个参数,所以可以只指定返回值的类型来调用add():

cpp 复制代码
auto result { add<long long>(1, 2) };

只有在推断参数为参数列表中排在最后时才有效。假定函数模板定义如下:

cpp 复制代码
template <typename T1, typename RetType, typename T2>
RetType add(const T1& t1, const T2& t2) { return t1 + t2; }

就需要指定RetType,因为编译器无法推断出其类型。然而,因为RetType是第二个参数,所以也必须显示指定T1:

cpp 复制代码
auto result { add<int, long long>(1, 2) };

也可以为返回类型模板参数提供一个缺省值,这样的话就可以不指定任何类型来调用add():

cpp 复制代码
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) };
相关推荐
“αβ”2 分钟前
海量数据面试题
c++·面试·职场和发展
虾球xz3 分钟前
游戏引擎学习第12天
android·学习·游戏引擎
清灵xmf15 分钟前
为什么 Vue3 封装 Table 组件丢失 expose 方法呢?
开发语言·前端·javascript·封装·eltable
楚疏笃17 分钟前
鸿蒙学习基本概念
学习·华为·harmonyos
祭の25 分钟前
新版Apache tomcat服务安装 Mac+Window双环境(笔记)
笔记·tomcat·apache
神仙别闹26 分钟前
基于JAVA实现的(GUI)坦克大战游戏
java·开发语言·游戏
凡人的AI工具箱36 分钟前
15分钟学 Go 第 54 天 :项目总结与经验分享
开发语言·人工智能·后端·算法·golang
小春学渗透39 分钟前
DAY110代码审计-PHP框架开发篇&ThinkPHP&版本缺陷&不安全写法&路由访问&利用链
开发语言·安全·web安全·php
奈葵41 分钟前
C语言字符函数和字符串函数
c语言·开发语言
2401_8791036844 分钟前
24.11.15 Vue3
笔记