c++11-17 模板核心知识(十三)—— 名称查找与ADL
在C++中,如果编译器遇到一个名称,它会寻找这个名称代表什么。比如x*y,如果x和y是变量的名称,那么就是乘法。如果x是一个类型的名称,那么就声明了一个指针。
C++是一个context-sensitive的语言 : 必须知道上下文才能知道表达式的意义。那么这个和模板的关系是什么呢?构造一个模板必须知道几个上下文:
- 模板出现的上下文
- 模板被实例化的上下文
- 实例化模板参数的上下文
名称分类
引入两个重要的概念:
- qualified name : 一个名称所属的作用域被显式的指明,例如
::、->或者.。this->count就是一个qualified name,但count不是,因为它的作用域没有被显示的指明,即使它和this->count是等价的。 - dependent name:依赖于模板参数。例如:
std::vector<T>::iterator. 但假如T是一个已知类型的别名(using T = int),那么就不是dependent name.

名称查找
名称查找有很多细节,这里我们只关注几个主要的点。
ordinary lookup
对于qualified name来说,会有显示指明的作用域。如果作用域是一个类,那么基类也会被考虑在内,但是类的外围作用域不会被考虑:
int x;
class B {
public:
int i;
};
class D : public B {};
void f(D *pd) {
pd->i = 3; // finds B::i
D::x = 2; // ERROR: does not find ::x in the enclosing scope
}
这点很符合直觉。
相反,对于非qualified name来说,会在外围作用域逐层查找(假如在类成员函数中,会先找本类和基类的作用域)。这叫做ordinary lookup :
extern int count; // #1
int lookup_example(int count) // #2
{
if (count < 0) {
int count = 1; // #3
lookup_example(count); // unqualified count refers to #3
}
return count + ::count; // the first (unqualified) count refers to #2 ;
} // the second (qualified) count refers to #1
这个例子也很符合直觉。
但是下面这个例子就没那么正常:
template<typename T>
T max (T a, T b) {
return b < a ? a : b;
}
namespace BigMath {
class BigNumber {
...
};
bool operator < (BigNumber const&, BigNumber const&);
...
}
using BigMath::BigNumber;
void g (BigNumber const& a, BigNumber const& b) {
...
BigNumber x = ::max(a,b);
...
}
这里的问题是:当调用max时,ordinary lookup不会找到BigNumber的operator <。如果没有一些特殊规则,那么在C++ namespace场景中,会极大的限制模板的适应性。ADL就是这个特殊规则,用来解决此类的问题。
ADL (Argument-Dependent Lookup)
ADL出现在C++98/C++03中,也被叫做Koenig lookup,应用在非qualified name上(下文简称unqualified name)。在函数调用表达式中(f(a1, a2, a3, ... ),包含隐式的调用重载operator,例如 << ),ADL应用一系列的规则来查找unqualified function names。
ADL会将函数表达式中实参的associated namespaces和associated classes加入到查找范围,这也就是为什么叫Argument-Dependent Lookup. 例如:某一类型是指向class X的指针,那么它的associated namespaces和associated classes会包含X和X所属的任何class和namespace.
对于给定的类型,associated classes和associated namespaces按照一定的规则来定义,大家可以看下官网Argument-dependent lookup,实在有点多,不写在这里了。理解为什么需要ADL、什么时候应用到ADL时,按照对应的场景再去查就行~
额外需要注意的一点是,ADL会忽略using :
#include <iostream>
namespace X {
template <typename T> void f(T);
}
namespace N {
using namespace X;
enum E { e1 };
void f(E) { std::cout << "N::f(N::E) called\n"; }
} // namespace N
void f(int) { std::cout << "::f(int) called\n"; }
int main() {
::f(N::e1); // qualified function name: no ADL
f(N::e1); // ordinary lookup finds ::f() and ADL finds N::f(), the latter is preferred
}
namespace N中的using namespace X会被ADL忽略,所以在main函数中,X::f()不会被考虑。
官网的例子
看下官网的例子帮助理解:
#include <iostream>
int main() {
std::cout << "Test\n"; // There is no operator<< in global namespace, but ADL
// examines std namespace because the left argument is in
// std and finds std::operator<<(std::ostream&, const char*)
operator<<(std::cout, "Test\n"); // same, using function call notation
// however,
std::cout << endl; // Error: 'endl' is not declared in this namespace.
// This is not a function call to endl(), so ADL does not apply
endl(std::cout); // OK: this is a function call: ADL examines std namespace
// because the argument of endl is in std, and finds std::endl
(endl)(std::cout); // Error: 'endl' is not declared in this namespace.
// The sub-expression (endl) is not a function call expression
}
注意最后一点(endl)(std::cout);,如果函数的名字被括号包起来了,那也不会应用ADL。
再来一个:
namespace A {
struct X;
struct Y;
void f(int);
void g(X);
}
namespace B {
void f(int i) {
f(i); // calls B::f (endless recursion)
}
void g(A::X x) {
g(x); // Error: ambiguous between B::g (ordinary lookup)
// and A::g (argument-dependent lookup)
}
void h(A::Y y) {
h(y); // calls B::h (endless recursion): ADL examines the A namespace
// but finds no A::h, so only B::h from ordinary lookup is used
}
}
这个比较好理解,不解释了。
ADL的缺点
依赖ADL有可能会导致语义问题,这也是为什么有的时候需要在函数前面加::,或者一般推荐使用xxx::func,而不是using namespace xxx 。因为前者是qualified name,没有ADL的过程。
引用现代C++之ADL中的例子,只看swap就行,类的其他函数可以略过:
#include <iostream>
namespace A {
template<typename T>
class smart_ptr {
public:
smart_ptr() noexcept : ptr_(nullptr) {
}
smart_ptr(const T &ptr) noexcept : ptr_(new T(ptr)) {
}
smart_ptr(smart_ptr &rhs) noexcept {
ptr_ = rhs.release(); // 释放所有权,此时rhs的ptr_指针为nullptr
}
smart_ptr &operator=(smart_ptr rhs) noexcept {
swap(rhs);
return *this;
}
void swap(smart_ptr &rhs) noexcept { // noexcept == throw() 保证不抛出异常
using std::swap;
swap(ptr_, rhs.ptr_);
}
T *release() noexcept {
T *ptr = ptr_;
ptr_ = nullptr;
return ptr;
}
T *get() const noexcept {
return ptr_;
}
private:
T *ptr_;
};
// 提供一个非成员swap函数for ADL(Argument Dependent Lookup)
template<typename T>
void swap(A::smart_ptr<T> &lhs, A::smart_ptr<T> &rhs) noexcept {
lhs.swap(rhs);
}
}
// 开启这个注释,会引发ADL冲突
//namespace std {
// // 提供一个非成员swap函数for ADL(Argument Dependent Lookup)
// template<typename T>
// void swap(A::smart_ptr<T> &lhs, A::smart_ptr<T> &rhs) noexcept {
// lhs.swap(rhs);
// }
//
//}
int main() {
using std::swap;
A::smart_ptr<std::string> s1("hello"), s2("world");
// 交换前
std::cout << *s1.get() << " " << *s2.get() << std::endl;
swap(s1, s2); // 这里swap 能够通过Koenig搜索或者说ADL根据s1与s2的命名空间来查找swap函数
// 交换后
std::cout << *s1.get() << " " << *s2.get() << std::endl;
}
(完)
朋友们可以关注下我的公众号,获得最及时的更新:

c++11-17 模板核心知识(十三)—— 名称查找与ADL的更多相关文章
- c++11-17 模板核心知识(十四)—— 解析模板之依赖型模板名称(.template/->template/::template)
tokenization与parsing 解析模板之类型的依赖名称 Dependent Names of Templates Example One Example Two Example Three ...
- c++11-17 模板核心知识(十五)—— 解析模板之依赖型类型名称与typename Dependent Names of Types
模板名称的问题及解决 typename规则 C++20 typename 上篇文章c++11-17 模板核心知识(十四)-- 解析模板之依赖型模板名称 Dependent Names of Templ ...
- c++11-17 模板核心知识(十一)—— 编写泛型库需要的基本技术
Callables 函数对象 Function Objects 处理成员函数及额外的参数 std::invoke<>() 统一包装 泛型库的其他基本技术 Type Traits std:: ...
- c++11-17 模板核心知识(十二)—— 模板的模板参数 Template Template Parameters
概念 举例 模板的模板参数的参数匹配 Template Template Argument Matching 解决办法一 解决办法二 概念 一个模板的参数是模板类型. 举例 在c++11-17 模板核 ...
- c++11-17 模板核心知识(二)—— 类模板
类模板声明.实现与使用 Class Instantiation 使用类模板的部分成员函数 Concept 友元 方式一 方式二 类模板的全特化 类模板的偏特化 多模板参数的偏特化 默认模板参数 Typ ...
- c++11-17 模板核心知识(一)—— 函数模板
1.1 定义函数模板 1.2 使用函数模板 1.3 两阶段翻译 Two-Phase Translation 1.3.1 模板的编译和链接问题 1.4 多模板参数 1.4.1 引入额外模板参数作为返回值 ...
- c++11-17 模板核心知识(八)—— enable_if<>与SFINAE
引子 使用enable_if<>禁用模板 enable_if<>实例 使用Concepts简化enable_if<> SFINAE (Substitution Fa ...
- c++11-17 模板核心知识(三)—— 非类型模板参数 Nontype Template Parameters
类模板的非类型模板参数 函数模板的非类型模板参数 限制 使用auto推断非类型模板参数 模板参数不一定非得是类型,它们还可以是普通的数值.我们仍然使用前面文章的Stack的例子. 类模板的非类型模板参 ...
- c++11-17 模板核心知识(五)—— 理解模板参数推导规则
Case 1 : ParamType是一个指针或者引用,但不是universal reference T& const T& T* Case 2 : ParamType是Univers ...
随机推荐
- python 关键字yield
问题 Python 关键字 yield 的作用是什么?用来干什么的? 比如,我正在试图理解下面的代码: def node._get_child_candidates(self, distance, m ...
- 【故障公告】Memcached 的“惹祸”,不知在为谁背锅
在 .NET 5.0 背锅 . Memcached 的惹祸 .缓存雪崩之后,我们没有找到问题的真正原因,我们知道没有找到根源的故障总是会再次光临的,不是在这周就是在下周,也许就在双11前后. 就在今天 ...
- CentOS修改镜像源头
CentOS6改为阿里镜像源: 1. cd /etc/yum.repos.d 2. mv CentOS-Base.repo CenOS-Base.repo.bak 3. wget http://mir ...
- netfilter内核态与用户态 通信 之 sockopt
用户态与内核态交互通信的方法不止一种,sockopt是比较方便的一个,写法也简单.缺点就是使用 copy_from_user()/copy_to_user()完成内核和用户的通信, 效率其实不高, 多 ...
- GC 的认识(转) https://github.com/qcrao/Go-Questions/blob/master/GC/GC.md#1-什么是-gc有什么作用
1. 什么是 GC,有什么作用? GC,全称 Garbage Collection,即垃圾回收,是一种自动内存管理的机制. 当程序向操作系统申请的内存不再需要时,垃圾回收主动将其回收并供其他代码进行内 ...
- binary hacks读数笔记(readelf命令)
可以用readelf命令来查看elf文件内容,跟objdump相比,这个命令更详细. 1. readelf -h SimpleSection.o ELF Header: Magic: 7f 45 4c ...
- 头秃了,二十三张图带你从源码了解Spring Boot 的启动流程~
持续原创输出,点击上方蓝字关注我 目录 前言 源码版本 从哪入手? 源码如何切分? 如何创建SpringApplication? 设置应用类型 设置初始化器(Initializer) 设置监听器(Li ...
- 动态导航栏和JavaScript箭头函数
动态导航栏和JavaScript箭头函数 今天我们来写一下动态的导航栏,并且学一下JavaScript的箭头函数等相关问题. 样式如下所示: html中执行代码如下所示: <!DOCTYPE h ...
- Tomcat AJP 文件包含漏洞复现(CVE-2020-1938)
漏洞原理 Tomcat配置了两个Connecto,它们分别是HTTP和AJP. HTTP默认端口为8080,处理http请求:AJP默认端口8009,用于处理 AJP 协议的请求. AJP比http更 ...
- 企业级工作流解决方案(十一)--集成Abp和ng-alain--权限系统服务
权限系统主要定义为管理员增删改查权限数据,直接读取数据库,权限系统服务主要定义为供其他系统调用的权限验证接口,定义为两个不同的微服务. 权限系统有一个特点,数据变动比较小,数据量本身并不是很大,访问量 ...