如果我写下面的代码:
/** Min of two elements */
template <typename T>
T min(const T a, const T b)
{
return (a < b) ? a : b;
}
/** Min of multiple elements */
template <typename T, typename... Ts>
T min(const T first, const Ts... others)
{
T result = first;
auto temp = { (result = min(result, others), 0)... };
(void)temp;
return result;
}
然后创建一个具有自己的覆盖的新类
min
:
/** Vec2 */
struct Vec2
{
float x, y;
Vec2(const float _x, const float _y) : x(_x), y(_y) {}
};
/** Min override for Vec2 */
Vec2 min(const Vec2 a, const Vec2 b)
{
return Vec2(min(a.x, b.x), min(a.y, b.y));
}
最后,在
main
:
extern Vec2 getNewVec2();
int main()
{
Vec2 v1 = getNewVec2();
Vec2 v2 = getNewVec2();
Vec2 v3 = getNewVec2();
Vec2 v_min = min(v1, v2, v3);
}
一切都按预期编译。但是,如果我将
min
函数包装在 math
命名空间内(包括 Vec2
的覆盖),并将对 min
的调用替换为 math::min
,则它无法编译,并显示“不匹配 '操作员<' (operand types are 'const Vec2' and 'const Vec2')".
看起来带有多个参数的
min
函数在编译时正在寻找对 math::min
的合适调用(以 auto temp = ...
开头的行)并且无法找到 Vec2
覆盖,因此它尝试使用顶部的通用模板版本,无法编译,因为没有 <
运算符重载 Vec2
。
如果我将
min
below 的多参数版本移至 Vec2
的 min
重载,那么无论我是否使用命名空间,它都会再次正确编译。尽管上面的代码片段旨在位于多个文件中,但这实际上并不是一个可行的解决方案,但它确实支持这样的理论:引入命名空间后,模板函数的顺序突然变得重要。
有没有办法允许多参数
min
函数在使用 Vec2
命名空间时找到 math
覆盖,就像在全局命名空间中一样?
导致问题的不是命名空间本身,而是调用函数的方式。
如果您将
min
称为不合格名称,则除了通常的不合格名称查找之外,还会执行 ADL。
模板内的 ADL 查找是从实例化点执行的,而不是从定义点执行的。然而,通常的限定查找仅从模板的定义点执行。 因此,在两个函数模板中,如果您稍后声明
Vec2
重载,那么它们只能通过 ADL 找到新的重载,而不能通过通常的非限定查找。因为您正确地将
min
的 Vec2
重载放在与 Vec2
类本身相同的命名空间范围中,所以 ADL 在需要时始终会找到它。但是,如果您使用限定名称调用函数,则不会执行 ADL。因此,如果在 min
模板本身之后声明,则不可能找到
Vec2
的 min
重载。要按照您想要的方式保持 min
可针对其他类型进行自定义,您需要在
min
模板中使用非限定调用,并将 min
的 Vec2
重载保留在与 Vec2
相同的命名空间范围内。这也正是操作符重载的工作原理。然而,选择这个名字min
有点危险。标准库中已经有一个名为
min
的函数,如果您尝试将 std::
中的类型与 min
一起使用,该函数也将通过 ADL 参与,可能会导致意外的重载决策选择。