C++ Logo

std-discussion

Advanced search

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 09:00:28 +0200
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. 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 07:00:43