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) };
相关推荐
写代码的小王吧12 分钟前
【Java可执行命令】(十)JAR文件签名工具 jarsigner:通过数字签名及验证保证代码信任与安全,深入解析 Java的 jarsigner命令~
java·开发语言·网络·安全·web安全·网络安全·jar
小卡皮巴拉20 分钟前
【力扣刷题实战】矩阵区域和
开发语言·c++·算法·leetcode·前缀和·矩阵
努力搬砖的咸鱼31 分钟前
Qt中的数据解析--XML与JSON处理全攻略
xml·开发语言·qt·json
Pacify_The_North32 分钟前
【C++进阶三】vector深度剖析(迭代器失效和深浅拷贝)
开发语言·c++·windows·visualstudio
Song38 分钟前
JVM 学习计划表(2025 版)
jvm·学习
神里流~霜灭39 分钟前
蓝桥备赛指南(12)· 省赛(构造or枚举)
c语言·数据结构·c++·算法·枚举·蓝桥·构造
一人の梅雨40 分钟前
化工网平台API接口开发实战:从接入到数据解析‌
java·开发语言·数据库
扫地的小何尚44 分钟前
NVIDIA工业设施数字孪生中的机器人模拟
android·java·c++·链表·语言模型·机器人·gpu
小杨爱学习zb1 小时前
学习总结 网格划分+瞬态求解设置
笔记·学习·算法
Zfox_1 小时前
【C++项目】从零实现RPC框架「四」:业务层实现与项目使用
linux·开发语言·c++·rpc·项目