#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 只能代表单一类型,无法同时表示 int 和 double。换言之,函数模板中两个形参都是 T,必须解析为同一实际类型。
你可能疑惑为何编译器不生成 max<double>(double, double),再通过数值转换把 int 提升为 double。答案是:模板实参推导阶段不进行类型转换;类型转换仅在重载决议时考虑。这种设计有意为之:
- 简化规则——要么完全匹配,要么失败;
- 允许我们强制要求多个形参类型相同。
解决方案
我们有三种解决方案。
方案一:用 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。改用两个形参 T 和 U:
#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,结果错误。
关键洞察T 与 U 独立解析,可相同也可不同。
修正:返回类型推导
让编译器根据返回语句推导返回类型:
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>
}
重载规则:编译器优先选择更受限/更特化的模板。
若无法确定谁更特化,则报二义性错误。
