4 - 默认模板参数

本文主要介绍了默认模板参数,以及如何在类模板和函数模板上使用它,最后还介绍了decltype()关键字。

介绍

在定义模板时,除了可以定义类型T,还能定义一些模板参数。例如在构造一个vector时,我们实际上使用了它的默认分配器。

使用

函数模板

默认模板参数就和普通函数的默认参数一样,一旦一个参数有了默认参数,它之后的参数都必须有默认参数。

给刚刚写的函数模板加一个默认参数:

using func_type = std::function<void(int&)>;
template<typename iter_type, typename func_type = func_type>
void for_each(iter_type first, iter_type last, func_type func = [](int& elem) {
	++elem;
})
{
	for (auto iter = first; iter != last; ++iter)
	{
		func(*iter);
	}
}

然后就能不指定第三个参数直接用了:

mystd::for_each(ivec.begin(), ivec.end());

类模板

也很简单,例如可以效仿std::vector,给自己的vector也加一个allocator

template<typename T, typename allocator_type = std::allocator<T>>
class MyVector
{
public:
	template<typename T2>
	void outPut(const T2& elem);
};

// 类外定义
template<typename T, typename allocator_type>
template<typename T2>
void MyVector<T, allocator_type>::outPut(const T2& elem)
{
	std::cout << elem << std::endl;
}

decltype关键字

有个比较大小的函数模板:

template <typename T1, typename T2, typename RT>
RT max(const T1& a, const T2& b)
{
	return a > b ? a : b;
}

此时使用它会报错(例如max(1, 1.2)),因为编译器可以推导出T1和T2的类型,但RT的类型无法推导出来。因此可以给RT一个默认模板参数(如 RT = double)以“解决”问题,但这种代码必然没有鲁棒性,如果比较的是字符串呢。

这时候 decltype便派上用场,该关键字可以获取表达式的类型,由于它是不求值操作数,会让括号内的东西不会被求值,所以不必担心效率问题:

template <typename T1, typename T2, typename RT = decltype(true ? T1{} : T2{})>
RT max(const T1& a, const T2& b)
{
	return a > b ? a : b;
}

如上述代码所示,decltype尝试获取表达式true ? T1{} : T2{}的类型,这里利用到三目表达式的类型返回规则,会 返回T1和T2的公共类型(例如int和double会返回double)。其实这段代码还是有缺点的,例如这里的T1和T2类型如果没有默认构造函数就会报错,在后面的文章会用到std::declval来优化这一点。

上面那一堆模板参数很长,有没有什么简便的方法:

  • C++11的后置返回类型:

    template <typename T1, typename T2>
    auto max(const T1& a, const T2& b) -> decltype(true ? a : b)	// 这里auto只是占位符, 不起实际作用
    {
    	return a > b ? a : b;
    }

    使用这一特性的好处就是不用再“构造”两个对象了,可以直接用a和b,并且代码量少了也会舒服点。但需要注意的是,这里和上面的返回类型是不一样的,上面的是简单的T1/T2,而这里返回的是 const T1/T2&。

  • C++14的返回类型推导 + C++20的简写函数模板:

    decltype(auto) max(const auto& a, const auto& b)
    {
    	return a > b ? a : b;
    }

    这种写法更精简了,注意这里的返回值decltype(auto),如果是auto,就遵循默认的模板参数推导规则,返回T类型;如果是decltype(auto),则返回T&类型。

参考资料

  • 飘零的落花 - 现代C++详解
  • Mq-b/Modern-Cpp-templates-tutorial: 现代C++模板教程 (github.com)
  • C++20高级编程