我正在尝试将 memchr 重新实现为 constexpr (1)。我没有预料到会出现问题,因为我已经成功地使用 strchr 完成了同样的事情,这非常相似。
但是 clang 和 gcc 都拒绝将 const void* 转换为 constexpr 函数中的任何其他内容,这阻止了我访问实际值。
我知道在 constexpr 函数中使用 void* 很奇怪,因为我们无法执行 malloc 并且无法将任意数据指定为文字值。我这样做基本上是作为练习的一部分,以尽可能多地重写 constexpr (2)。
我仍然想知道为什么这是不允许的以及是否有任何解决办法。
谢谢!
(1) 我的 memchr 实现:
constexpr void const *memchr(const void *ptr, int ch, size_t count) {
const auto block_address = static_cast<const uint8_t *>(ptr);
const auto needle = static_cast<uint8_t>(ch);
for (uintptr_t pos{0}; pos < count; ++pos) {
auto byte_address = block_address + pos;
const uint8_t value = *byte_address;
if (needle == value) {
return static_cast<void const *>(byte_address);
}
}
return nullptr;
}
(2) Github 上的整个项目:https://github.com/jlanik/constexprstring
不,在常量表达式中不可能以这种方式使用
void*
。在常量表达式中禁止从 void*
转换为其他对象指针类型。 reinterpret_cast
也被禁止。
这可能是故意使在编译时无法访问对象表示。
您不能在编译时拥有具有通常签名的
memchr
。
我认为你能做的最好的事情就是编写指向
char
及其 cv 限定版本以及 std::byte
(作为重载或作为模板)的指针的函数,而不是 void*
。
对于指向其他类型对象的指针,在某些情况下会很棘手,并且在大多数情况下不可能实现
memchr
的确切语义。
虽然我不确定这是否可能,但也许在
memchr
的模板化版本中,人们可以通过 std::bit_cast
将指针传递的对象的底层字节读取到包含 std::byte
/ 的结构中unsigned char
适当大小的数组。
我最近正在尝试
constexpr
std::any
实现并遇到了同样的问题。经过一些研究1,事实证明,C++26 中允许将常量表达式中的 void 指针转换为指向对象的指针类型,前提是新类型类似于 point-to-反对。提案 p2738 包含对常量表达式要求的以下更改,并已在最新版本的 Clang 中实现。
表达式
是核心常量表达式,除非计算E
,遵循抽象机[intro.execution]的规则,将 评估以下其中一项: [...]E
- 从类型
cv void* 到对象指针类型的转换“指向 cv void 的指针”类型的纯右值到对象指针类型P
除非T
指向对象其类型类似于P
;T
godbolt 上带有 clang(trunk) 版本的演示
#include <cstdint>
constexpr void const *memchr(const void *ptr, int ch, std::size_t count) {
const auto block_address = static_cast<const unsigned char *>(ptr);
const auto needle = static_cast<unsigned char>(ch);
for (uintptr_t pos{0}; pos < count; ++pos) {
auto byte_address = block_address + pos;
const unsigned char value = *byte_address;
if (needle == value) {
return static_cast<void const *>(byte_address);
}
}
return nullptr;
}
constexpr const unsigned char haystack[] = "ABCDEFG";
static_assert(static_cast<const unsigned char*>(memchr(haystack, 'D', 7)) - haystack == 3);
当然,如果你的库是针对 C++14 的,这并没有多大帮助,但对于标题中的问题来说,这仍然是一个值得更新的内容。
请注意,这不允许转换为基类的指针,因为将 void 指针直接转换为基类可能会产生与首先转换为派生类指针然后转换为基类不同的指针。如提案中的示例所示。
#include <cassert>
struct A {
virtual void f() {};
int a;
};
struct B {
int b;
};
struct C: B, A {};
int main() {
C c;
void* v = &c;
assert(static_cast<B*>(v) == static_cast<B*>(static_cast<C*>(v))); // Fails
}
1 此页表明它已被 C++26 采用,强烈支持,没有反对。