C++实参依赖查找英文缩写为ADL,在查找无限定函数(包括对重载运算符的隐式函数调用)时,通常无限定名字查找所考虑的作用域和命名空间之外,还在其各个实参的命名空间中查找这些函数,例如:

namespace ns1
{
    template<class T>
    void foo(T)
    {
        std::cout << "ns1::foo" << std::endl;
    }
    
    struct X {};
}

ns1::X x;
foo(x); //全局命名空间并没有定义foo,但ADL会继续在实参的命名空间中查找
        //因为这个实参的类型是ns1::X,所以会继续在ns1命名空间中查找foo

实参依赖查找使得使用定义于不同命名空间的运算符成为可能,例如

namespace ns1
{
    struct X { int32_t value; };
    
    const X operator+(const X &lhs, const X &rhs)
    {
        return X { lhs.value + rhs.value };
    }
}

ns1::X x1{10}, x2{20};
auto x3 = x1 + x2; //等价于 x3 = operator+(x1, x2);
                   //全局命名空间中没有operator+(const X&, const X&)
                   //但因为实参属于ns1命名空间,所以ADL会检验ns1命名空间,并找到operator+(const X&, const

ADL 是在泛型代码中为交换两个对象而建立的手法能成立的原因:

using std::swap;
swap(obj1, obj2);
namespace ns1
{
	struct Object
	{
		...
		void swap(Object &obj) noexcept;
		...
	};

	void swap(Object &lhs, Object &rhs) noexcept
	{
		lhs.swap(rhs);
	}
}

template<class T>
void foo(T &obj1, T &obj2)
{
	using std::swap;
	swap(obj1, obj2); //无限定函数(未使用名字空间修饰)
	                  //根据ADL,此swap可以在T所属的命名空间中匹配函数
	                  //若无匹配的函数,将调用标准库版swap(std::swap)
}

int32_t a = 1, b = 2;
Object obj1, obj2;

foo(a, b); //调用的是std::swap
foo(obj1, obj2); //调用的是ns1::swap(Object &lhs, Object &rhs)

参考:

实参依赖查找

Argument-dependent name lookup - Wikipedia