I'm trying to reimplement memchr as constexpr (1). I haven't expected issues as I have already successfully done the same thing with strchr which is very simmilar.
However both clang and gcc refuse to cast const void* to anything else within constexpr function, which prevents me to access the actual values.
I understand that working with void* in constexpr function is weird, as we cannot do malloc and there is no way to specify arbitrary data as literal values. I'm doing this basically as a part of an excercise to rewrite as much as I can from as constexpr (2).
Still I'd like to know why this is not allowed and if there is any way around it.
Thank you!
(1) My implementation of 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) The entire project on Github: https://github.com/jlanik/constexprstring
I am recently experimenting with a constexpr std::any implementation and has hit the same issue. After some research1, it turns out it will be allowed in C++26 to cast a pointer-to-void to a pointer-to-object type in a constant expression, provided that the new type is similar to pointed-to object. The propsal p2738 contains the following changes to the requirements of constant expressions and has been implemented in the newest version of Clang.
An expression
Eis a core constant expression unless the evaluation ofE, following the rules of the abstract machine[intro.execution], would evaluate one of the following: [...]
- a conversion from type
cv void* to a pointer-to-object typea prvaluePof type ”pointer to cv void” to a pointer-to-object typeTunlessPpoints to an object whose type is similar toT;
Demo with clang(trunk) version on godbolt
#include <cstddef>
#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);
Of course, if your library is targeting C++14, this does not help much, but it's still a worthwhile update for the question in the title.
Note that this will not allow casting to a pointer of base class because casting a pointer-to-void directly to a base class may yield a different pointer from first casting to a pointer to a derived class then to the base class, as is shown in the following example in the proposal.
#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 This page indicates it has been adopted for C++26 with strong favor and no against.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With