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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 2/10/20 3:39 PM, Jonathan Wakely wrote:
Do you really need constant initialization, or just static initialization?

Modulo any subtle differences I don't understand (likely) between the two, just the latter. I need the address of the pointed-to S* const object to be set in the bits for the struct/class instance in the .data segment of the compiled ELF binary.


It seems all you require is there's no dynamic initialization at run
time, you don't actually *require* constexpr to be used here.

With the caveats above, yes, correct.


That's a
means to an end, which in this case doesn't actually work.

Yes, as I've been wrestling with for two years now. I only recently discovered the fact that my initialization not being a constexpr was the root of the problem, and I still don't understand why reinterpret_cast needs to be disallowed in a constexpr (i.e. what would break if it was allowed). Nor likewise why reinterpret_cast to a pointer type is illegal (I get one or both errors, depending on compiler and details of the attempted definition). But you've provided a lot of tutorial explanation already, so this is more of a comment than a request for more.


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; }

All true. This was a stripped-down, minimal example for the purpose of illustration. In the real code the struct S is far more complex, in fact likely one of the heavily templated objects from https://github.com/thanks4opensource/regbits_stm

Likewise, the gratuitous extra member function was because the actual enclosing class will be using the pointed-to object many times, in many different methods. If I have to use this workaround I'd much rather have a (I hope and trust) inline method to access the pointer vs sprinkling verbose reinterpret_casts throughout the code.


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.

Yes, of course there's no private type specifier in C. My point was that in C, using C semantics and programming practices, the implementation is simple and straightforward. In C++ it seems I have to use one form of trickery or another to work around what, IMHO, is a failing of the language: That I can't use simple/obvious/common C++ semantics/practices and have both a private Object* const pointer and also static initialization, the "defined behavior" of which I claim is, while not portable across machine architectures, completely unambiguous.

The relative superiority/inferiority is a matter of subjective taste. For myself I'll almost certainly stick with C++ even if it requires using the workarounds. I wish it was otherwise, and also want to understand better why it isn't (and/or can't be changed).


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.

Once again (as per the Bugzilla posts) thanks for your clever (that's intended as a true compliment, not sarcasm) ways of getting around the problem. The first gets right back to requiring the per-use reinterpret_casts or helper method, and the second uses union type-punning, as you point out. I'm a caveman and will use anything I need to get the job done, but from what I've read, using unions for this purpose is almost more frowned-upon than anything else. Regardless, it's once again an extra level of conceptual indirection, extra code, potential for errors, and just a workaround in general. My C++-hating C coder friends point at the complexity and verbosity of C++ and I counter with the advantages it brings. I find it hard to do so in this case.

I don't have a C++20-compliant version of GCC yet, and hadn't heard of constinit until seeing it here in your post. It seems very new and not heavily documented, but from what I read it's more a static_assert kind of thing (I'm speaking generally, not rigorously) that makes sure an object is static initializable. Is it more than that, and by using it would the following code compile and be static initialized?

class C {
  public:
    constexpr C(
    unsigned* const u)
    :   _u(u)
    {}
    void set() const { *_u = 0x87654321; }
  protected:
    unsigned* const     _u;
};

// or c = ...
// or c {...}
// or whatever
constinit C   c(reinterpret_cast<unsigned*>(0x40000000));







[Index of Archives]     [Linux C Programming]     [Linux Kernel]     [eCos]     [Fedora Development]     [Fedora Announce]     [Autoconf]     [The DWARVES Debugging Tools]     [Yosemite Campsites]     [Yosemite News]     [Linux GCC]

  Powered by Linux