The Dreaded Two-Phase Name Lookup

Friday, December 18, 2009

The Dreaded Two-Phase Name Lookup

C++ has more than its fair share of dark, dank corners, especially where templates are concerned. One of the most vexing is “two-phase name lookup”, which involves lookup for any names that occur in the body of a template. As you might expect, there are two different phases of templates:

  1. Template definition time: when the template is initially parsed, long before it is instantiated, the compiler parses the template and looks up any “non-dependent” names. A name is “non-dependent” if the results of name lookup do not depend on any template parameters, and therefore will be the same from one template instantiation to another.
  2. Template instantiation time: when the template is instantiated, the compiler looks up any “dependent” names, now that it has the full set of template arguments to perform lookup. The results of this lookup can (and often do!) vary from one template instantiation to another.

Two-phase name lookup is not as complicated as its reputation implies. There are some non-obvious rules in the determination of what is a dependent vs. a non-dependent name, but otherwise the idea is simple. The problem with two-phase name lookup is that current compiler support for this feature is very poor. For example, GCC implements two-phase name lookup relatively well, but occasionally delays lookups that should have been done at template definition time (phase 1) until template instantiation time or performs lookups in both phases when it shouldn’t. Visual C++, on the other hand, has a template parsing model that delays nearly every lookup to instantiation time (phase 2). By not implementing two-phase name lookup fully, both compilers tend to accept incorrect template code, and in some cases will end up compiling code differently from the mythical fully-conforming compiler. This is a portability issue, both between those two compilers (Visual C++ is more lenient) and to other, more pedantic compilers.

Like Clang. Clang was designed with complete support for two-phase name lookup, parsing template definitions (phase 1) as completely as possible and only performing name lookup at template instantiation time (phase 2) when required. Since we have chosen to make Clang C++ strict, we end up diagnosing template problems that other compilers miss. While that’s generally good—correct code is more portable code—it also means that Clang needs to try extra-hard to produce decent diagnostics. Here’s a recent problem Clang found within the LLVM code base (which compiled with GCC):

In file included from llvm/lib/Analysis/AliasAnalysisCounter.cpp:16:
In file included from llvm/include/llvm/Pass.h:369:
In file included from llvm/include/llvm/PassAnalysisSupport.h:24:
llvm/include/llvm/ADT/SmallVector.h:317:7: error: use of undeclared identifier 'setEnd'
setEnd(this->end()+1);
^
this->
In file included from llvm/lib/Analysis/AliasAnalysisCounter.cpp:16:
In file included from llvm/include/llvm/Pass.h:369:
llvm/include/llvm/PassAnalysisSupport.h:56:14: note: in instantiation of member function 'llvm::SmallVectorImpl llvm::PassInfo const *>::push_back' requested here
Required.push_back(ID);
^
In file included from llvm/lib/Analysis/AliasAnalysisCounter.cpp:16:
In file included from llvm/include/llvm/Pass.h:369:
In file included from llvm/include/llvm/PassAnalysisSupport.h:24:
llvm/include/llvm/ADT/SmallVector.h:105:8: note:
must qualify identifier to find this declaration in dependent base class
void setEnd(T *P) { this->EndX = P; }
^

The problem itself is in SmallVectorImpl, in the call to setEnd(). The actual setEnd() function isn’t in SmallVectorImpl, but in a base class, so we have a situation that looks like this:

template<typename T>
class SmallVectorTemplateCommon {
protected:
 void setEnd(T *P);
};
template<typename T>
class SmallVectorImpl : public SmallVectorTemplateCommon<T> {
public:
 void push_back(const T& value) {
  // ...
  setEnd(this->end() + 1);
 }
};

If we weren’t in a template, this code would be fine, because we would find setEnd in our base class. However, because we’re in a template we’re dealing with two-phase name lookup. While parsing push_back(), the compiler performs name lookup for the name “setEnd” at phase 1: however, it can’t find anything because it isn’t allowed to look into the dependent base class SmallVectorTemplateCommon. However, this code is still valid: “setEnd” is taken as the name of a non-member function, which could be found at instantiation time via Argument Dependent Lookup. Unfortunately, when we do get around to instantiating push_back, Argument Dependent Lookup doesn’t look into our base class, so Clang gives us a “use of undeclared identifier” error.

By itself, that error would leave the programmer scratching her head. GCC and Visual C++ accepted this code, and setEnd() is obviously in the base class, so what gives? To help out a bit, Clang gives more detail:

  1. The note at the very end, which reads “must qualify identifier to find this declaration in dependent base class,” tells the programmer what declaration Clang could find… if only she were to qualify the name somehow so that Clang were allowed to look there.
  2. The original error had a little hint below the caret diagnostic, in green, providing advice on how to fix this particular issue. By adding “this->”, we tell the compiler that “setEnd” is in the current class or one of its (possibly dependent) base classes, to be found at template instantiation time.

Clang C++ is designed to be a strict but helpful compiler, following the letter of the C++ standard to help programmers make sure that their code is portable. We also hope to make Clang a friendly compiler, that can use its knowledge of the program and the C++ language to help programmers get past portability problems like this one. And maybe, just maybe, Clang can shine a little light into the dark, scary corners of C++.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值