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