首发于vczh的日常
考不上三本也能懂系列——什么是C++的argument-dependent lookup

考不上三本也能懂系列——什么是C++的argument-dependent lookup

你现在所阅读的并不是第一篇文章,你可能想看目录和前言

最近还是继续在开发自己那个“doxygen”,因为我自己的代码几乎没用argument-dependent lookup (ADL),所以我本来想随便做一做的,后来发现事情没有这么简单。首先操作符重载就严重依赖于ADL,所以我很难说自己的代码没用上。其次ranged-base for loop也严重依赖于ADL,考虑到以后我所有的FOREACH宏要全部修改成ranged-base for loop,那只能上了。

所有内容参考自Argument-dependent lookup

简单来说,ADL说的是这样的一个事情,当你写下这样的代码的时候:

Fuck(bitch);

你需要找出Fuck所有可能的结果然后来确定哪个重载比较好。在这里Fuck可以简单地理解为必须是单个的名字,也就是前面不能有“::”。另一种情况就是a+b,当他不能解释为a.operator+(b)的时候,编译器就会把它改写成operator+(a, b),于是就符合这个情况。

首先编译器会按照正常的方法看一下Fuck是什么东西,如果找到的所有Fuck都是全局函数,没有任何其它类型的符号的时候,就会激活ADL。

简单来说,ADL会扫描你所有的参数的类型,把涉及到的类型所在的最里层namespace统统都加入到一个列表里。如果这个类型有父类,那就继续扫描父类。如果这个类型有包含类,那就跳过包含类一直往上爬到第一个namespace。如果这个类型是其他类型譬如说指针、函数指针、成员函数指针,那么涉及到的所有类型会通通加入搜查范围。还有一些其他情况略去不讲。

在你最终得到所有目标namespace之后,编译器毁在这些namespace里面找Fuck函数(如果不是函数就当没看见),最后跟你原先找到的Fuck全部一起试一遍,看看哪个重载好。

下面我们来看一个简单的例子

namespace fuck
{
	namespace bitch
	{
		struct Bitch;
	}

	namespace cunt
	{
		struct Cunt {};

		extern float Fuck(void(*)(bitch::Bitch), int);
	}

	namespace penis
	{
		struct Penis {};

		extern char Fuck(void(*)(bitch::Bitch), void*);
	}

	namespace bitch
	{
		struct Bitch : cunt::Cunt, penis::Penis {};
	}
}

void Fuck(...);
void Shit(fuck::bitch::Bitch) {}

auto x = Fuck(Shit, 0);
auto y = Fuck(Shit, nullptr);

显然,x和y分别是float和char,他不会看到全局的Fuck(...)。因为函数(指针)Shit含有类型Bitch,而一个Bitch既有Cunt又有Penis,所以fuck::cunt和fuck::penis两个namespace都会被看到,最终就会在三个Fuck之间进行重载的挑选。

实践证明,这一点VC++2017的做法跟C++标准几乎是一致的。既然我开发的是GacUI的代码索引和文档生成工具,当然要贴合VC++的做法。我采取了一个十分滑稽但是行之有效的办法:github.com/vczh-librari

ASSERT_OVERLOADING的三个参数是这样的,第一个参数是代码,第二个参数是我把第一个参数做语法分析变成AST之后重新生成回代码的字符串(用于确保parser没理解错),第三个参数是这个表达式应该有的类型。VC++当然会帮我计算出这个表达式的类型,如果跟第三个参数不一样会直接造成编译错误。运行的时候跟我自己的编译器结果比一下,看看是不是一样,不一样就崩溃在那里。

这个宏的实现原理超级简单,同学们可以自己阅读代码看看能不能看懂,看不懂我下次再写一篇文章解释一下(逃

同样的手法我还用来测试很多关于其他重载和类型升级等琐碎而且无聊还容易搞错的C++标准里面的内容,当regression test简直是不能更方便。

编辑于 2019-02-21

文章被以下专栏收录