多模板形参函数模板

#include <iostream>

template <typename T>
T max(T x, T y)
{
    return (x < y) ? y : x;
}

int main()
{
    std::cout << max(1, 2)     << '\n'; // 实例化 max<int>
    std::cout << max(1.5, 2.5) << '\n'; // 实例化 max<double>
    return 0;
}

现在考虑下面看似相似的程序:

#include <iostream>

template <typename T>
T max(T x, T y)
{
    return (x < y) ? y : x;
}

int main()
{
    std::cout << max(2, 3.5) << '\n';  // 编译错误
}

你可能会惊讶地发现这段代码无法通过编译,编译器会输出一大堆(可能令人困惑的)错误信息。在 Visual Studio 上作者得到:

Project3.cpp(11,18): error C2672: 'max': 找不到匹配的重载函数
Project3.cpp(11,28): error C2782: 'T max(T,T)': 模板形参 'T' 存在歧义
...

在调用 max(2, 3.5) 时,我们传入了两种不同类型:一个 int 和一个 double。由于未使用尖括号显式指定类型,编译器首先查找是否存在非模板匹配 max(int, double),未找到;随后尝试模板实参推导,同样失败,因为模板形参 T 只能代表单一类型,无法同时表示 intdouble。换言之,函数模板中两个形参都是 T,必须解析为同一实际类型。

你可能疑惑为何编译器不生成 max<double>(double, double),再通过数值转换把 int 提升为 double。答案是:模板实参推导阶段不进行类型转换;类型转换仅在重载决议时考虑。这种设计有意为之:

  1. 简化规则——要么完全匹配,要么失败;
  2. 允许我们强制要求多个形参类型相同。

解决方案

我们有三种解决方案。

方案一:用 static_cast 把实参转换为同一类型

std::cout << max(static_cast<double>(2), 3.5) << '\n';

现在两实参均为 double,编译器实例化 max<double>(double, double)
缺点:调用端冗长、可读性差。

方案二:显式指定模板实参

std::cout << max<double>(2, 3.5) << '\n';

显式指定 T = double,编译器直接实例化 max<double>,然后把 int 隐式转换为 double
比方案一简洁,但仍需思考类型。

方案三:使用多模板形参

根源在于仅用了一个模板形参 T。改用两个形参 TU

#include <iostream>

template <typename T, typename U>
T max(T x, U y)           // x 为 T,y 为 U
{
    return (x < y) ? y : x; // 窄化转换警告
}

int main()
{
    std::cout << max(2, 3.5) << '\n'; // 实例化 max<int, double>
}

此时 T=int, U=double,编译器乐意实例化 max<int, double>。但返回类型被声明为 T=int,导致 3.5 被窄化为 3,结果错误。

关键洞察
TU 独立解析,可相同也可不同。

修正:返回类型推导

让编译器根据返回语句推导返回类型:

template <typename T, typename U>
auto max(T x, U y)
{
    return (x < y) ? y : x;
}

auto 返回类型需完整定义可见(不能仅靠前向声明)。

高级:显式返回类型

若需前向声明,可用 std::common_type_t

#include <type_traits>

template <typename T, typename U>
auto max(T x, U y) -> std::common_type_t<T, U>;

C++20 缩写函数模板

C++20 允许用 auto 形参自动生成模板:

auto max(auto x, auto y)
{
    return (x < y) ? y : x;
}

等价于:

template <typename T, typename U>
auto max(T x, U y) { ... }

若要求两形参同类型,则无简洁缩写形式。

最佳实践
C++20 及以后,若各形参应独立类型,优先使用缩写函数模板。

函数模板可重载

模板之间也可重载,只要模板形参个数或函数形参列表不同:

#include <iostream>

template <typename T>
auto add(T x, T y) { return x + y; }

template <typename T, typename U>
auto add(T x, U y) { return x + y; }

template <typename T, typename U, typename V>
auto add(T x, U y, V z) { return x + y + z; }

int main()
{
    std::cout << add(1.2, 3.4) << '\n';   // 匹配 add<double>
    std::cout << add(5.6, 7) << '\n';     // 匹配 add<double,int>
    std::cout << add(8, 9, 10) << '\n';   // 匹配 add<int,int,int>
}

重载规则:编译器优先选择更受限/更特化的模板。
若无法确定谁更特化,则报二义性错误。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"