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