C++ Logo

std-discussion

Advanced search

Re: Legality of a near-zero-cost C wrapper leveraging P0593R6 implicit object creation

From: François Plumerault <francois.plumerault_at_[hidden]>
Date: Thu, 24 Jul 2025 16:44:27 +0200
Hello,

Thank you for pointing out that the size of an empty class is not
guaranteed (I wasn't aware of this).
Assuming the size of the two empty classes is 1 (using static assertion):
```cpp
static_assert(sizeof(pub::DummyClass) == 1, "Unsupported platform");
static_assert(sizeof(pub::DummyClass2) == 1, "Unsupported platform");
```
Does the pattern (calls A-F) become well-defined? I am especially curious
about the validity of my interpretation of P0593R6.

Best regards,
François Plumerault

Le jeu. 24 juil. 2025 à 14:35, Jens Maurer <jens.maurer_at_[hidden]> a écrit :

>
>
> On 24.07.25 09:00, François Plumerault via Std-Discussion wrote:
> > Hello,
> >
> > I am creating an interface class for a C implementation and I have found
> a pattern that I have never seen it in the wild, so I would like to
> determine whether or not it is in fact well-defined.
> > This is based on "6.7.2 Object model" (10 and 13) which permits implicit
> object creation within suitable implicit-lifetime storage.
> >
> >
> > Object model 6.7.2/13: "An operation that begins the lifetime of an
> array of unsigned char or std::byte implicitly creates objects within the
> region of storage occupied by the array"
> >
> > Object model 6.7.2/10: "Some operations are described as implicitly
> creating objects within a specified region of storage. For each operation
> that is specified as implicitly creating objects, that operation implicitly
> creates and starts the lifetime of zero or more objects of
> implicit-lifetime types (6.8.1) in its specified region of storage if doing
> so would result in the program having defined behavior. [...]."
> >
> >
> > The pattern is divided into two parts, an internal implementation and a
> public one.
> >
> > ```cpp
> > ///
> ------------------------------------------------------------------------------
> > /// Here is the internal implementation:
> > ///
> ------------------------------------------------------------------------------
> >
> > #include <iostream>
> >
> > namespace prv {
> > class Dummy {
> > public:
> > virtual ~Dummy() {} // Virtual stuff to emphasise that this
> class can be anything that provides one-byte storage.
> > virtual void doStuff() { std::cout << "doStuff()" <<
> '\n'; }
> > virtual void doStuff() const { std::cout << "doStuff() const" <<
> '\n'; }
> >
> > unsigned char pubStorage[1]; // area containing the "implicit
> lifetime type"
> > };
> >
> > inline Dummy* getDummy() { // single instance
> > static Dummy d{};
> > return &d;
> > }
> > } // prv
> >
> > extern "C" {
> > struct core_dummy_s;
> >
> > void core_get_dummy(core_dummy_s** out) {
> > auto* d = prv::getDummy();
> > *out = reinterpret_cast<core_dummy_s*>(&d->pubStorage[0]);
> > }
> >
> > void core_get_const_dummy(core_dummy_s const** out) {
> > auto* d = prv::getDummy();
> > *out = reinterpret_cast<core_dummy_s const*>(&d->pubStorage[0]);
> > }
> >
> > void core_const_dummy_do_stuff(core_dummy_s const* in) {
> > auto* storage = reinterpret_cast<char const*>(in);
> > auto* d = reinterpret_cast<prv::Dummy const*>(storage -
> offsetof(prv::Dummy, pubStorage)); // 17.2.4 "offsetof" on non-standard
> layout is conditionally supported, but that's not the issue here.
> > d->doStuff();
> > }
> >
> > void core_dummy_do_stuff(core_dummy_s* in) {
> > auto* storage = reinterpret_cast<char*>(in);
> > auto* d = reinterpret_cast<prv::Dummy*>(storage -
> offsetof(prv::Dummy, pubStorage));
> > d->doStuff();
> > }
> > }
> >
> > ///
> ------------------------------------------------------------------------------
> > /// Here the public implementation:
> > ///
> ------------------------------------------------------------------------------
> >
> >
> > namespace pub {
> > class DummyClass { // Implicit lifetime type of size and alignment 1
> > protected:
> > DummyClass() = default;
> >
> > public:
> > void doStuff() const {
> core_const_dummy_do_stuff(reinterpret_cast<core_dummy_s const*>(this)); }
> > void doStuff() {
> core_dummy_do_stuff(reinterpret_cast<core_dummy_s*>(this)); }
> > };
> >
> > DummyClass const& getConstDummy() {
> > core_dummy_s const* p = nullptr;
> > core_get_const_dummy(&p);
> > return *reinterpret_cast<DummyClass const*>(p);
> > }
> >
> > DummyClass& getDummy() {
> > core_dummy_s* p = nullptr;
> > core_get_dummy(&p);
> > return *reinterpret_cast<DummyClass*>(p);
> > }
> >
> > // Implicit lifetime type of size and alignment 1 derived from the first
> one
> > class DummyClass2 : public DummyClass {
> > private:
> > DummyClass2() = default;
> >
> > public:
> > void doMoreStuff() const {
> core_const_dummy_do_stuff(reinterpret_cast<core_dummy_s const*>(this)); }
> > void doMoreStuff() {
> core_dummy_do_stuff(reinterpret_cast<core_dummy_s*>(this)); }
> > };
> >
> > DummyClass2 const& getConstDummy2() {
> > core_dummy_s const* p = nullptr;
> > core_get_const_dummy(&p);
> > return *reinterpret_cast<DummyClass2 const*>(p);
> > }
> > } // pub
> >
> > int main() {
> > const auto& c1 = pub::getConstDummy();
> > c1.doStuff(); // (A)
> >
> > auto& m1 = pub::getDummy();
> > c1.doStuff(); // (B)
> > m1.doStuff(); // (C)
> >
> > const auto& c2 = pub::getConstDummy2();
> > c1.doStuff(); // (D)
> > m1.doStuff(); // (E)
> > c2.doStuff(); // (F)
> >
> > return 0;
> > }
> > ```
> >
> > My understanding is that creating a 'DummyClass2' within the 'char[1]'
> storage gives the program well-defined behaviour.
>
> That is not a portable assumption. I don't think we say anywhere that an
> empty
> class has size 1 (as opposed to, say, 4).
>
> Jens
>
>
> > Therefore, the program creates a 'DummyClass2' and has well-defined
> behaviour.
> > I would like to confirm (or deny) that it complies with the
> implicit-lifetime semantics as described by P0593R6, in particular
> regarding the legality of calls (A)-(F).
> > Are the calls well‑defined? If not, where is the UB introduced?
> >
> > Thanks in advance for your insights.
> >
> > Best regards,
> > François Plumerault
> >
>
>

Received on 2025-07-24 14:44:41