Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cast from const void* in a constexpr expression? [duplicate]

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

like image 588
jlanik Avatar asked Oct 27 '25 05:10

jlanik


1 Answers

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 E is a core constant expression unless the evaluation of E, 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 type a prvalue P of type ”pointer to cv void” to a pointer-to-object type T unless P points to an object whose type is similar to T;

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.

like image 181
Weijun Zhou Avatar answered Oct 28 '25 19:10

Weijun Zhou