Date: Thu, 24 Jul 2025 14:35:26 +0200
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
>
> 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 12:35:29