在纯 C++ 中实现 Linux 内核的 __is_constexpr (ICE_P) 宏

Implementing the Linux Kernel's __is_constexpr (ICE_P) macro in pure C++

Martin Uecker 的 ICE_P 谓词的标准 C11 版本之后,我尝试用纯 C++ 实现它。 C11版本,使用_Generic选择如下:

#define ICE_P(x) _Generic((1? (void *) ((x)*0) : (int *) 0), int*: 1, void*: 0)

C++的明显做法是用模板替换_Genericdecltype,如:

template<typename T> struct is_ice_helper;
template<> struct is_ice_helper<void*> { enum { value = false }; };
template<> struct is_ice_helper<int*>  { enum { value = true  }; };

#define ICE_P(x) (is_ice_helper<decltype(1? (void *) ((x)*0) : (int *) 0)>::value)

然而,simplest test 失败了。为什么它不能检测整型常量表达式?

这个问题很微妙。确定条件表达式的指针操作数的复合类型的规范在 C++ 中与 C 中的类似,所以它开始看起来很有希望:

(N4659) [expr.cond]

7 Lvalue-to-rvalue, array-to-pointer, and function-to-pointer standard conversions are performed on the second and third operands. After those conversions, one of the following shall hold:

  • [...]

  • One or both of the second and third operands have pointer type; pointer conversions, function pointer conversions, and qualification conversions are performed to bring them to their composite pointer type (Clause [expr]). The result is of the composite pointer type.

  • [...]

还原为复合指针类型指定如下:

(N4659) [expr]

5 The composite pointer type of two operands p1 and p2 having types T1 and T2, respectively, where at least one is a pointer or pointer to member type or std​::​nullptr_­t, is:

  • if both p1 and p2 are null pointer constants, std​::​nullptr_­t;
  • if either p1 or p2 is a null pointer constant, T2 or T1, respectively;
  • if T1 or T2 is “pointer to cv1 void” and the other type is “pointer to cv2 T”, where T is an object type or void, “pointer to cv12 void”, where cv12 is the union of cv1 and cv2;
  • [...]

所以我们的 ICE_P 宏的结果取决于我们按顺序检查上面的哪一颗子弹。鉴于我们如何定义 is_ice_helper,我们知道复合类型不是 nullptr_t,否则我们会击中第一颗子弹,并且会由于缺少模板特化而出错。所以我们必须击中第 3 个项目符号,使谓词报告为假。这一切似乎都取决于空指针常量的定义。

(N4659) [conv.ptr] (emphasis mine)

1 A null pointer constant is an integer literal with value zero or a prvalue of type std​::​nullptr_­t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type. Such a conversion is called a null pointer conversion. Two null pointer values of the same type shall compare equal. The conversion of a null pointer constant to a pointer to cv-qualified type is a single conversion, and not the sequence of a pointer conversion followed by a qualification conversion. A null pointer constant of integral type can be converted to a prvalue of type std​::​nullptr_­t.

由于根据上面的定义 (int*)0 不是空指针常量,我们不符合 [expr]/5 的第一个项目符号。复合类型是notstd::nullptr_t(void *) ((x)*0) 既不是空指针常量,也不能变成空指针常量。删除演员表(定义不允许的东西)给我们留下 (x)*0。这是一个值为零的整数常量表达式。但它不是零值的 整数文字 ! C++中空指针常量的定义与C中的有偏差!

(N1570) 6.3.2.3 Pointers

3 An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.

C 允许任意 值为零的常量表达式构成空指针常量,而C++ 要求整型文字。鉴于 C++ 对计算各种文字类型的常量表达式的丰富支持,这似乎是一个不必要的限制。并且使上述 ICE_P 方法成为 C++ 中的非入门者。