C++的两段名称查找[two-phrase name lookup]

原理

C++在涉及模板的特化及模板类的实例化时,通过两段查找来确定其定义中的每个命名。顺序大致为:
1. 第一步是模板的特化。此时只需要进行名称替换,也即将模板的形参以实参代替。此时编译器查找所有non-dependent命名,也即非成员函数或变量,并替换其类型或参数类型;
1. 第二步是模板类的实例化。在这之前编译器会进行常规的基类查找,并将所有dependent命名进行解析,也即成员函数或变量的查找(要么在本类的定义中找到,要么在基类或虚表中找到)。

详细原理参见文章:The Dreaded Two-Phase Name Lookup

这种两端查找对带继承的模板类开发有很大影响。有时我们会发现如下代码的错误:

template <typename T>
class Base {
    void foo1();
    void foo2();
};

template <typename T>
class Derived : public Base<T> {
    virtual void foo1();
    void bar() {
        foo1();         // ok
        foo2();         // error
        this->foo1();   // ok
        this->foo2();   // ok
    }
};

int main(...) {
    Derived<int>().bar();   // error occurs here
}

直接调用foo2产生的错误如下:

foo2();

/path/to/def.cpp:427:55: note: declarations in dependent base ‘Derived<int>’ are not found by unqualified lookup
/path/to/def.cpp:427:55: note: use ‘this->foo2’ instead

上述的错误即源于这个two-phrase name lookup。编译器在编译main函数的唯一一行代码时,首先特化Derived的模板,此时未在其定义内部找到foo2函数,故抛出错误。好在编译器给了友好的提示,我们将其改为this-foo2()之后,即编译通过。这么做的主要变化是将foo2这个命名从non-dependent弱化为dependent,从而延后它的解析。

不使用while的循环(setjmp的用法)

setjmplongjmp是ANSI C程序支持/控制库中的一对函数,用于保存env与返回。setjmp将当前env保存在一个jmp_buf中;longjmp返回后的env则从中取出,完全相同。这是一些有栈协程实现的基础。

setjmp和longjmp有一些有趣的应用,如替代while实现循环。如下为示例代码,来自cppreference

#include <iostream>
#include <csetjmp>

std::jmp_buf jump_buffer;

[[noreturn]] void a(int count) 
{
    std::cout << "a(" << count << ") called\n";
    std::longjmp(jump_buffer, count+1);  // setjump() 将返回 count+1
}

int main()
{
    volatile int count = 0; // 在 setjmp 作用域中被修改的局部变量必须是 volatile
    if (setjmp(jump_buffer) != 9) { // 在一个 if 内不等于常量表达式
        a(++count);  // 这会导致 setjmp() 退出
    }
}

程序是经过验证可以执行的,执行结果与cppreference给出的一致。

a(1) called
a(2) called
a(3) called
a(4) called
a(5) called
a(6) called
a(7) called
a(8) called

后续再过协程的时候还要返回来看看,在此mark。

Python map方法真心好用

Python下有个map方法,结合一些单目操作符或单参数的函数对iterable实例进行操作真的非常好用。

  1. 如下代码将通过localtime获得的时间信息数组转换为字符串数组:
','.join(map(str, time.localtime()))
  1. 如下进一步控制了转换后字符串的格式,注意分隔符为空,仅转换前六位元素(年月日时分秒):
''.join(map('{0>2d}'.format, time.localtime()[0,6])) # 好吧我承认可以使用strftime
一些备注
  • 新版string format方法的确方便了很多,可以灵活地控制输出格式。如上{0>2d}表示对应位置的整型值至少显示两位,否则左侧补零;
  • 如果希望采用双目或多目运算符,抑或使用多参数的方法,猜测应该需要采用placeholder,或者传入元素为同维度元组的数组。这一点纯猜,没有验证;
  • map的实现应该很简单,但是大大地方便了用户。至于其实现,其实类似stl alogrithm里的函数了,如std::transform或std::for_each等,均可以实现这个效果。不过map这个字眼老实说让用户非常直观地猜到它的意图。能及时跟上概念的演进也是新语言(相对c/c++)的优势吧。