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) };
相关推荐
霸道流氓气质19 小时前
Spring AI Alibaba 学习路线图:从入门到精通
人工智能·学习·spring
Engineer邓祥浩19 小时前
宏观认知(二):AI项目落地与团队协作——吴恩达《AI for Everyone》Week2学习笔记
人工智能·笔记·学习
z落落19 小时前
C# ArrayList 动态集合(接口/区别/API/深浅拷贝)+ List<T> 泛型集合
开发语言·c#
Cx330❀19 小时前
【Linux网络】从零构建高性能UDP服务器:从Echo到英译汉业务级实现
大数据·linux·服务器·开发语言·网络·c++·udp
basketball61619 小时前
Golang:基础语法总结
开发语言·后端·golang
兰令水19 小时前
leecodecode【双指针题2】【2026.5.26打卡-java版本】
java·开发语言·算法
不吃土豆的马铃薯19 小时前
TCP 三次握手 / 四次挥手详解
服务器·开发语言·网络·c++·网络协议·tcp/ip
中屹指纹浏览器19 小时前
隐性风控:解析指纹浏览器IP与环境参数冲突BUG及全套适配方案
经验分享·笔记
WMX101219 小时前
Unity-shader学习记录
学习·unity·游戏引擎
羑悻的小杀马特19 小时前
【动态规划篇】正则表达式与通配符:开启代码匹配的赛博奇幻之旅
c++·算法·leetcode·正则表达式