How to constexpr construct C++ object containing reinterpret_cast pointer?

Jonathan Wakely jwakely.gcc@gmail.com
Mon Feb 10 23:39:00 GMT 2020


On Mon, 10 Feb 2020 at 19:16, mark_r via gcc-help <gcc-help@gcc.gnu.org> wrote:
>
> I need to define a C++ class with the following properties:
>
> 1) Global/static instances are constexpr constructed in the .data segment,
> fully initialized, without runtime execution of a constructor.

Do you really need constant initialization, or just static initialization?

It seems all you require is there's no dynamic initialization at run
time, you don't actually *require* constexpr to be used here. That's a
means to an end, which in this case doesn't actually work.


> 2) A private or protected reference or const pointer member (not a pointer
> to const or const reference) to a different object at an arbitrary memory
> location.
>
> The following example fails because reinterpret_cast is not allowed in a
> constexpr:
>
> ```
> struct S { unsigned u; };
>
> class C {
>   public:
>     constexpr C(
>     S* const s)
>     :   _s(s)
>     {}
>     void set() const { _s->u = 0x87654321; }
>   protected:
>     S* const     _s;
> };
>
> constexpr S* const s = reinterpret_cast<S*>(0x40000000);  // compile error
> C   c(s);
>
> int main()
> {
>     c.set();
> }
> ```
>
> Removing constexpr by changing the above to:
>
> S* const s = reinterpret_cast<S*>(0x40000000);
>
> will compile, but violates requirement #1 -- the object is compiled into the
> .bss segment and a constructor is generated which must be called by
> pre-main() startup code.
>
> Using brace-initialization (either C-style with "=", or C++ uniform
> iniitialization without) does compile the object, initialized, in .data but
> all members must be public -- if any members are private/protected the
> brace-initialization will fail to compile.

The form of initialization (i.e. braces or not) doesn't matter, what
matters is whether the type is an aggregate.


> ```
> struct S { unsigned u; };
>
> struct C {
>     void set() const { s->u = 0x87654321; }
>     S* const     s;
> };
>
> C c = { reinterpret_cast<S*>(0x40000000) };
>
> int main()
> {
>     c.set();
> }
> ```
>
> The closest thing to a solution I've found is:
>
> ```
> struct S { unsigned u; };
>
> class C {
>   public:
>     constexpr C(
>     unsigned    u)
>     :   _u(u)
>     {}
>     void set() const { _s()->u = 0x87654321; }
>   protected:
>     volatile S* _s() const { return reinterpret_cast<S*>(_u);}
>     const unsigned  _u;
> };
>
> C c(0x40000000);
>
> int main()
> {
>     c.set();
> }
> ```
>
> But this requires the extra "_s()" method which adds complexity and obscures
> the code's intent.

You don't need the extra member function, you just chose to use that.
You could also write:

  void set() const { *reinterpret_cast<S*>(_u)->u = 0x87654321; }

I'm not sure why struct S is needed here at all:

  void set() const { *reinterpret_cast<unsigned*>(_u) = 0x87654321; }

> Note that in GCC 4.8.3, prior to the changes for standards compliance as per
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=49171, the constexpr cast did
> not cause compilation error and the first example above worked as desired.
> I've looked at the upcoming C++20 bit_cast operator, but
> https://docs.w3cub.com/cpp/numeric/bit_cast/ specifically states it will not
> allow pointers.
>
> I welcome suggestions for workarounds with current or planned GCC versions
> that satisfy both the requirements stated above. I had advocated for a GCC
> extension in the Bugzilla URL, but was advised to post here instead. Pending
> answers, I may file a separate Bugzilla request or discuss this further on
> the general GCC mailing list or in the C++ community at large.
>
> Note that the use-case for this is in embedded microcontroller programming,
> where hardware resources appear as memory-accessed structs at arbitrary
> locations in the address space. I frequently advocate for C++ as an equal or
> superior language to C for this kind of low-level coding (likewise for
> operating system kernels and drivers). Many disagree, and pending some new
> solution I haven't found, in this specific case I'll have to reluctantly
> agree.

You can't make the member private, which you can't do in C either. I
fail to see how that makes C++ inferior. It seems equal in this
respect, if not better (you can still use member functions on the
type).

If being unable to make the member private makes the solution inferior
to the C version, then surely the C version is inferior to itself.
That's a silly requirement.

Anyway, this works:

struct Addr
{
  constexpr Addr(unsigned p) : u(p) { }

  void set() const { *reinterpret_cast<unsigned*>(u) = 0x87654321; }

private:
  uintptr_t const     u;
};

constinit Addr addr( 0x40000000 );

int main()
{
  addr.set();
}

And this works if you're OK with type-punning via a union:

struct Addr
{
  constexpr Addr(unsigned v) : u(v) { }

  void set() const { *p = 0x87654321; }

private:
  union {
    uintptr_t u;
    unsigned* p;
  };
};

constinit Addr addr( 0x40000000 );

int main()
{
  addr.set();
}

In both these cases, the C++20 'constinit' keyword ensures the object
is statically initialized, not at runtime. For pre-C++20 you can just
omit the 'constinit' and the result is the same.



More information about the Gcc-help mailing list