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

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

 



On Tue, 11 Feb 2020 at 05:25, mark_r wrote:
>
> 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).

It has been reported to be impossible to implement in one major
compiler implementation. Constexpr evaluation is effectively a
compile-time interpreter of C++, and has a stricter type system and
restrictions on which parts of the language are possible.

reinterpret_cast breaks the type system to tell the compiler that you
know something it doesn't about the actual types present at runtime.
That is not the case in constexpr evaluation, the compiler has
complete knowledge of the types involved, and knows you're lying.
0x4000000 is not a pointer value.

> Nor likewise why reinterpret_cast to a pointer type is illegal

Because integers are not pointers.

What you really want is a way to provide a literal pointer value, or
some other compile-time pointer value. There is no reason
reinterpret_cast has to be the way to do that. Again, using
reinterpret_cast is just a means to an end, not your end goal.


> (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,

So use the 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.

It's a non-standard extension supported by some compilers, including
GCC, but is not portable to all compilers.

> 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


Right, it's just an assertion that says you expect the object to be
statically initialized. If the compiler needs to do dynamic
initialization for a constinit variable, the program would be
ill-formed (as opposed to just silently changing from static
initialization to dynamic initialization if you accidentally make a
change to the code that requires dynamic init).

I used constinit to prove that the initialization is not dynamic.

> would the following code compile and be static initialized?

No, you're still trying to pass a non-constant to a constexpr constructor.

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