在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来说,会有显示指明的作用域。如果作用域是一个类,那么基类也会被考虑在内,但是类的外围作用域不会被考虑:

  1. int x;
  2. class B {
  3. public:
  4. int i;
  5. };
  6. class D : public B {};
  7. void f(D *pd) {
  8. pd->i = 3; // finds B::i
  9. D::x = 2; // ERROR: does not find ::x in the enclosing scope
  10. }

这点很符合直觉。

相反,对于非qualified name来说,会在外围作用域逐层查找(假如在类成员函数中,会先找本类和基类的作用域)。这叫做ordinary lookup :

  1. extern int count; // #1
  2. int lookup_example(int count) // #2
  3. {
  4. if (count < 0) {
  5. int count = 1; // #3
  6. lookup_example(count); // unqualified count refers to #3
  7. }
  8. return count + ::count; // the first (unqualified) count refers to #2 ;
  9. } // the second (qualified) count refers to #1

这个例子也很符合直觉。

但是下面这个例子就没那么正常:

  1. template<typename T>
  2. T max (T a, T b) {
  3. return b < a ? a : b;
  4. }
  5. namespace BigMath {
  6. class BigNumber {
  7. ...
  8. };
  9. bool operator < (BigNumber const&, BigNumber const&);
  10. ...
  11. }
  12. using BigMath::BigNumber;
  13. void g (BigNumber const& a, BigNumber const& b) {
  14. ...
  15. BigNumber x = ::max(a,b);
  16. ...
  17. }

这里的问题是:当调用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 namespacesassociated classes加入到查找范围,这也就是为什么叫Argument-Dependent Lookup. 例如:某一类型是指向class X的指针,那么它的associated namespacesassociated classes会包含X和X所属的任何class和namespace.

对于给定的类型,associated classesassociated namespaces按照一定的规则来定义,大家可以看下官网Argument-dependent lookup,实在有点多,不写在这里了。理解为什么需要ADL、什么时候应用到ADL时,按照对应的场景再去查就行~

额外需要注意的一点是,ADL会忽略using :

  1. #include <iostream>
  2. namespace X {
  3. template <typename T> void f(T);
  4. }
  5. namespace N {
  6. using namespace X;
  7. enum E { e1 };
  8. void f(E) { std::cout << "N::f(N::E) called\n"; }
  9. } // namespace N
  10. void f(int) { std::cout << "::f(int) called\n"; }
  11. int main() {
  12. ::f(N::e1); // qualified function name: no ADL
  13. f(N::e1); // ordinary lookup finds ::f() and ADL finds N::f(), the latter is preferred
  14. }

namespace N中的using namespace X会被ADL忽略,所以在main函数中,X::f()不会被考虑。

官网的例子

看下官网的例子帮助理解:

  1. #include <iostream>
  2. int main() {
  3. std::cout << "Test\n"; // There is no operator<< in global namespace, but ADL
  4. // examines std namespace because the left argument is in
  5. // std and finds std::operator<<(std::ostream&, const char*)
  6. operator<<(std::cout, "Test\n"); // same, using function call notation
  7. // however,
  8. std::cout << endl; // Error: 'endl' is not declared in this namespace.
  9. // This is not a function call to endl(), so ADL does not apply
  10. endl(std::cout); // OK: this is a function call: ADL examines std namespace
  11. // because the argument of endl is in std, and finds std::endl
  12. (endl)(std::cout); // Error: 'endl' is not declared in this namespace.
  13. // The sub-expression (endl) is not a function call expression
  14. }

注意最后一点(endl)(std::cout);,如果函数的名字被括号包起来了,那也不会应用ADL。

再来一个:

  1. namespace A {
  2. struct X;
  3. struct Y;
  4. void f(int);
  5. void g(X);
  6. }
  7. namespace B {
  8. void f(int i) {
  9. f(i); // calls B::f (endless recursion)
  10. }
  11. void g(A::X x) {
  12. g(x); // Error: ambiguous between B::g (ordinary lookup)
  13. // and A::g (argument-dependent lookup)
  14. }
  15. void h(A::Y y) {
  16. h(y); // calls B::h (endless recursion): ADL examines the A namespace
  17. // but finds no A::h, so only B::h from ordinary lookup is used
  18. }
  19. }

这个比较好理解,不解释了。

ADL的缺点

依赖ADL有可能会导致语义问题,这也是为什么有的时候需要在函数前面加::,或者一般推荐使用xxx::func,而不是using namespace xxx 。因为前者是qualified name,没有ADL的过程。

引用现代C++之ADL中的例子,只看swap就行,类的其他函数可以略过:


  1. #include <iostream>
  2. namespace A {
  3. template<typename T>
  4. class smart_ptr {
  5. public:
  6. smart_ptr() noexcept : ptr_(nullptr) {
  7. }
  8. smart_ptr(const T &ptr) noexcept : ptr_(new T(ptr)) {
  9. }
  10. smart_ptr(smart_ptr &rhs) noexcept {
  11. ptr_ = rhs.release(); // 释放所有权,此时rhs的ptr_指针为nullptr
  12. }
  13. smart_ptr &operator=(smart_ptr rhs) noexcept {
  14. swap(rhs);
  15. return *this;
  16. }
  17. void swap(smart_ptr &rhs) noexcept { // noexcept == throw() 保证不抛出异常
  18. using std::swap;
  19. swap(ptr_, rhs.ptr_);
  20. }
  21. T *release() noexcept {
  22. T *ptr = ptr_;
  23. ptr_ = nullptr;
  24. return ptr;
  25. }
  26. T *get() const noexcept {
  27. return ptr_;
  28. }
  29. private:
  30. T *ptr_;
  31. };
  32. // 提供一个非成员swap函数for ADL(Argument Dependent Lookup)
  33. template<typename T>
  34. void swap(A::smart_ptr<T> &lhs, A::smart_ptr<T> &rhs) noexcept {
  35. lhs.swap(rhs);
  36. }
  37. }
  38. // 开启这个注释,会引发ADL冲突
  39. //namespace std {
  40. // // 提供一个非成员swap函数for ADL(Argument Dependent Lookup)
  41. // template<typename T>
  42. // void swap(A::smart_ptr<T> &lhs, A::smart_ptr<T> &rhs) noexcept {
  43. // lhs.swap(rhs);
  44. // }
  45. //
  46. //}
  47. int main() {
  48. using std::swap;
  49. A::smart_ptr<std::string> s1("hello"), s2("world");
  50. // 交换前
  51. std::cout << *s1.get() << " " << *s2.get() << std::endl;
  52. swap(s1, s2); // 这里swap 能够通过Koenig搜索或者说ADL根据s1与s2的命名空间来查找swap函数
  53. // 交换后
  54. std::cout << *s1.get() << " " << *s2.get() << std::endl;
  55. }

(完)

朋友们可以关注下我的公众号,获得最及时的更新:

c++11-17 模板核心知识(十三)—— 名称查找与ADL的更多相关文章

  1. c++11-17 模板核心知识(十四)—— 解析模板之依赖型模板名称(.template/->template/::template)

    tokenization与parsing 解析模板之类型的依赖名称 Dependent Names of Templates Example One Example Two Example Three ...

  2. c++11-17 模板核心知识(十五)—— 解析模板之依赖型类型名称与typename Dependent Names of Types

    模板名称的问题及解决 typename规则 C++20 typename 上篇文章c++11-17 模板核心知识(十四)-- 解析模板之依赖型模板名称 Dependent Names of Templ ...

  3. c++11-17 模板核心知识(十一)—— 编写泛型库需要的基本技术

    Callables 函数对象 Function Objects 处理成员函数及额外的参数 std::invoke<>() 统一包装 泛型库的其他基本技术 Type Traits std:: ...

  4. c++11-17 模板核心知识(十二)—— 模板的模板参数 Template Template Parameters

    概念 举例 模板的模板参数的参数匹配 Template Template Argument Matching 解决办法一 解决办法二 概念 一个模板的参数是模板类型. 举例 在c++11-17 模板核 ...

  5. c++11-17 模板核心知识(二)—— 类模板

    类模板声明.实现与使用 Class Instantiation 使用类模板的部分成员函数 Concept 友元 方式一 方式二 类模板的全特化 类模板的偏特化 多模板参数的偏特化 默认模板参数 Typ ...

  6. c++11-17 模板核心知识(一)—— 函数模板

    1.1 定义函数模板 1.2 使用函数模板 1.3 两阶段翻译 Two-Phase Translation 1.3.1 模板的编译和链接问题 1.4 多模板参数 1.4.1 引入额外模板参数作为返回值 ...

  7. c++11-17 模板核心知识(八)—— enable_if<>与SFINAE

    引子 使用enable_if<>禁用模板 enable_if<>实例 使用Concepts简化enable_if<> SFINAE (Substitution Fa ...

  8. c++11-17 模板核心知识(三)—— 非类型模板参数 Nontype Template Parameters

    类模板的非类型模板参数 函数模板的非类型模板参数 限制 使用auto推断非类型模板参数 模板参数不一定非得是类型,它们还可以是普通的数值.我们仍然使用前面文章的Stack的例子. 类模板的非类型模板参 ...

  9. c++11-17 模板核心知识(五)—— 理解模板参数推导规则

    Case 1 : ParamType是一个指针或者引用,但不是universal reference T& const T& T* Case 2 : ParamType是Univers ...

随机推荐

  1. Netty源码解析 -- ChannelOutboundBuffer实现与Flush过程

    前面文章说了,ChannelHandlerContext#write只是将数据缓存到ChannelOutboundBuffer,等到ChannelHandlerContext#flush时,再将Cha ...

  2. 腾讯开源 APIJSON 连创五个第一

    腾讯第一个码云推荐项目,// 其它最早创建的是 TencentOS-tiny(码云) 2019.8.23 腾讯第一个码云GVP项目,// 其它最早创建的是 TencentOS-tiny(码云) 201 ...

  3. yaf拓展安装步骤

    1.wget http://pecl.php.net/get/yaf-3.0.7.tgz //下载yaf.tar 2.tar zxvf yaf-3.0.7.tgz //解压 3.cd yaf-3.0. ...

  4. 3、Django之路由层

    一 路由的作用 路由即请求地址与视图函数的映射关系,如果把网站比喻为一本书,那路由就好比是这本书的目录,在Django中路由默认配置在urls.py中. 二 简单的路由配置 # urls.py fro ...

  5. leetcode 43:construct-binary-tree-from-inorder

    题目描述 给出一棵树的中序遍历和后序遍历,请构造这颗二叉树 注意: 保证给出的树中不存在重复的节点 Given inorder and postorder traversal of a tree, c ...

  6. Java -- "final" 的理解

    Java具有继承和多态的特性,这也造就了Java语言的面向对象的灵活性.但是,过于灵活就意味的有失控的可能性. 于是,产生了final 的概念 -- 为了数据的绝对安全,无法被后期修改,英文称之为 m ...

  7. Core WebApi项目快速入门(一):环境部署

    1.WebApi新建与部署 1.1 新建Core WebApi工程 1.2 部署 1.2.1 IIS部署 首先以文件方式发布应用程序,然后下载依赖.net core运行时及host安装包 在iis中看 ...

  8. InnoDB事务的二阶段提交

    问题: 什么是二阶段提交 为什么需要二阶段提交 二阶段提交流程 什么是二阶段提交? ### 假设原来id 为10 的记录age 为5 begin; update student set age = 1 ...

  9. umask及文件默认和原始权限说明

    umask作用:设置了用户创建文件的默认权限.是权限的补码,一般在/etc/profile.$ [HOME]/.bash_profile或$[HOME]/.profile中设置umask值. 查看um ...

  10. Python 调用Get接口

    import requests,jsonurl = 'http://localhost:30627/api/jobs/GetNuberId?id=2'req = requests.get(url)re ...