命名空间会影响模板函数实例化的顺序吗?

问题描述 投票:0回答:1

如果我写下面的代码:

/** 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
覆盖,就像在全局命名空间中一样?

c++ templates argument-dependent-lookup
1个回答
0
投票

导致问题的不是命名空间本身,而是调用函数的方式。

如果您将

min
称为不合格名称,则除了通常的不合格名称查找之外,还会执行 ADL。

模板内的 ADL 查找是从实例化点执行的,而不是从定义点执行的。然而,通常的限定查找仅从模板的定义点执行。 因此,在两个函数模板中,如果您稍后声明

Vec2

重载,那么它们只能通过 ADL 找到新的重载,而不能通过通常的非限定查找。因为您正确地将

min
Vec2
重载放在与
Vec2
类本身相同的命名空间范围中,所以 ADL 在需要时始终会找到它。
但是,如果您使用限定名称调用函数,则不会执行 ADL。因此,如果在 

min

模板本身之后声明,则不可能找到

Vec2
min
重载。
要按照您想要的方式保持 

min

可针对其他类型进行自定义,您需要在

min
模板中使用非限定调用,并将
min
Vec2
重载保留在与
Vec2
相同的命名空间范围内。这也正是操作符重载的工作原理。
然而,选择这个名字

min

有点危险。标准库中已经有一个名为

min
的函数,如果您尝试将
std::
中的类型与
min
一起使用,该函数也将通过 ADL 参与,可能会导致意外的重载决策选择。
    

© www.soinside.com 2019 - 2024. All rights reserved.