stateful constexpr

通过stateful constexpr,我们希望实现以下功能

1
2
3
4
5
6
int main () {
constexpr int a = f ();
constexpr int b = f ();

static_assert (a != b, "fail");
}

通过在编译器实现副作用,我们可以在编译期实现容器、查看某个类是否已经被实例化查看一个函数在决议后的地址,甚至实现反射

实现f

我们f的实现如下

1
2
3
4
5
6
7
template<
bool B = noexcept (flag (0)),
int = sizeof (dependent_writer<B>)
>
constexpr int f () {
return B;
}

其中noexcept (X)用来检测里面的X是否是常量表达式。特别地,在这里,如果函数flag没有被定义,那么noexcept会返回false。容易想到,下面对dependent_writer<B>sizeof应该是为了去定义flag函数。下面我们来看这个类模板的实现。

1
2
3
4
5
6
7
8
9
constexpr int flag (int); // 1,flag只声明而未定义
template<class Tag>
struct writer {
friend constexpr int flag (Tag) { // 2,定义了flag
return 0;
}
};
template<bool B, class Tag = int>
struct dependent_writer : writer<Tag> { };

这段代码的核心点是,我们延迟了函数flag到我们实例化writer<Tag>的时候。其方式是声明函数flagwriter的友元,那么其定义应当随writer<Tag>的模板实例化进行。
现在,一个最直接的疑问点是为什么不直接使用writer<Tag>,这个涉及到在附注中列举的C++模板实例化的一些规则。其原因是使用裸的writer<int>会直接实例化,为了避免这个问题,我们需要延迟这个实例化到f的实现上,因此我们引入dependent_writer,它要依赖一个bool B才能实例化。

附注

C++ 模板实例化的规则

  1. 模板实例化与模板特化
    模板实例化(instantiation)指通过指定的模板参数实例化一个模板。此时(point of instantiation),编译器会为对应的模板特化生成real code。

    1
    2
    3
    4
    5
    template <typename T>
    struct test{ T m; };

    template test<int>; // explicit instantiation
    test<int> a; // implicit instantiation

    模板特化(specialization)依然是一个模板,它仍然需要进行实例化来得到real code。

    1
    2
    3
    template <typename T>
    struct test{ T m; };
    template <> struct test<int>{ int newM; } // specialization

    类比到函数上,实例化类似于调用,特化类似于重载(虽然特化还可以分为全特化和偏特化)

  2. point of instantiation
    一旦在一个需要实例化的上下文中refer了一个template specialization,就会产生一个point of instantiation,此时编译器就会为这个template specialization生成代码。
    下面作者的话有点难于理解,于是我直接参考标准temp.point理解,并且问了问题。对于(member) function template specialization X,如果他被另一个template specialization Y所refer,且这个Y依赖于某个模板参数,那么他的point of instantiation等同于Y。在同样的情况下,如果X是class template specialization,那么它应该在Y紧前面初始化。
    对于其他的情况,设X处在的名字空间的声明/定义位于D,如果X是一个函数,在D紧后面初始化;如果X是一个类,那么在D紧前面初始化。
    function template specialization可以有任意多次的实例化,但我们必须保证它们的结果是相同的,否则ill-formed。可以联想到inline函数也是这样的
  3. 如果class template specialization含有友元声明,那么它的所有友元将被对待为仿佛一个explicit specialization在point of instantiation处已经被声明。
    class template specialization只能有一次的实例化。

  4. 隐式实例化function template specialization的条件
    没有被显式特化或显式实例化。
    上下文需要它的定义。

  5. 隐式实例化class template specialization的条件
    没有被显式特化或显式实例化。
    上下文需要completely-defined的type。
    特别地,隐式实例化class template specialization只会实例化成员的声明而非定义,除非:

  6. 一个class template specialization的成员的定义是在它被需要时被隐式实例化。
    Q:是否可以认为当我们没有refer到writer时,它所有的成员的声明都没有被实例化?
    Q:是否可以认为当我们没有refer到writer的友元成员函数f时,f没有被实例化?

Reference

  1. http://b.atch.se/posts/non-constant-constant-expressions