Date: Fri, 25 Jul 2025 19:37:18 +0200
Hello,
Thank you for taking the time to review my example and for raising these
points.
> In fact, why does `prv::Dummy` need to be a separate type from
`core_dummy_s`?
> And why are you creating a C++ wrapper around a C API of a C++
implementation?
> Can't you remove the middle abstraction?
My original intent was to mimic a real‑world scenario in which a C++
library exposes a pure‑C ABI that is, in turn, used by another C++ library.
As you correctly note, that extra layer is not strictly necessary for
illustrating the question, so I have condensed the code to keep only the
elements relevant to the implicit‑lifetime discussion.
Following the comment from Jens Maurer, I also added the static_assert to
ensure that the storage is big enough for the wrapper classes.
Please see the updated example below:
```cpp
//
-----------------------------------------------------------------------------
// Internal implementation
//
-----------------------------------------------------------------------------
#include <cstdio>
struct core_dummy_s {
unsigned char pubStorage[1];
};
void doStuff(core_dummy_s* dummy) { std::puts("doStuff()"); }
void doConstStuff(const core_dummy_s* dummy) { std::puts("doStuff()
const"); }
inline core_dummy_s* getCoreDummy() // single instance
{
static core_dummy_s d{};
return &d;
}
extern "C" {
void core_get_dummy(unsigned char** out)
{
*out = &getCoreDummy()->pubStorage[0];
}
void core_get_const_dummy(const unsigned char** out)
{
*out = &getCoreDummy()->pubStorage[0];
}
void core_const_dummy_do_stuff(const unsigned char* in)
{
doConstStuff(reinterpret_cast<const core_dummy_s*>(in)); //
pointer‑interconvertible
}
void core_dummy_do_stuff(unsigned char* in)
{
doStuff(reinterpret_cast<core_dummy_s*>(in)); //
pointer‑interconvertible
}
}
//
-----------------------------------------------------------------------------
// Public interface
//
-----------------------------------------------------------------------------
namespace pub {
class DummyClass { // implicit‑lifetime type,
size/alignment 1
protected:
DummyClass() = default;
public:
void doStuff() const { core_const_dummy_do_stuff(reinterpret_cast<const
unsigned char*>(this)); }
void doStuff() { core_dummy_do_stuff(reinterpret_cast<unsigned
char*>(this)); }
};
inline const DummyClass& getConstDummy()
{
const unsigned char* p = nullptr;
core_get_const_dummy(&p);
return *reinterpret_cast<const DummyClass*>(p);
}
inline DummyClass& getDummy()
{
unsigned char* p = nullptr;
core_get_dummy(&p);
return *reinterpret_cast<DummyClass*>(p);
}
// A second implicit‑lifetime type derived from the first
class DummyClass2 : public DummyClass {
private:
DummyClass2() = default;
public:
void doMoreStuff() const {
core_const_dummy_do_stuff(reinterpret_cast<const unsigned char*>(this)); }
void doMoreStuff() {
core_dummy_do_stuff(reinterpret_cast<unsigned char*>(this)); }
};
inline const DummyClass2& getConstDummy2()
{
const unsigned char* p = nullptr;
core_get_const_dummy(&p);
return *reinterpret_cast<const DummyClass2*>(p);
}
} // namespace pub
static_assert(sizeof(pub::DummyClass) == 1, "Unsupported platform");
static_assert(sizeof(pub::DummyClass2) == 1, "Unsupported platform");
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)
}
```
> There is no `DummyClass` object at that location.
> This will probably work, but isn't legal.
Could you clarify why the lifetime‑implying rules in [basic.life] (6.7.2/10
and /13) do not let us treat the storage inside `core_dummy_s` as
implicitly containing a `DummyClass2`, and therefore also a `DummyClass`,
once that storage begins its lifetime?
>From my reading, those paragraphs allow an operation that "begins the
lifetime of an array of `unsigned char`" (here, the static initialization
of `pubStorage`) to implicitly create any implicit‑lifetime objects that
would give the program defined behaviour. This is what led me to believe
that a `DummyClass2`, and hence a base‑subobject `DummyClass`, would be
considered to exist at that address.
I would greatly appreciate any insight (especially a specific clause)
showing why this interpretation is incorrect or correct.
Thank you again for your time.
Best regards,
François Plumerault
Le ven. 25 juil. 2025 à 14:00, <std-discussion-request_at_[hidden]> a
écrit :
> Send Std-Discussion mailing list submissions to
> std-discussion_at_[hidden]
>
> To subscribe or unsubscribe via the World Wide Web, visit
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
> or, via email, send a message with subject or body 'help' to
> std-discussion-request_at_[hidden]
>
> You can reach the person managing the list at
> std-discussion-owner_at_[hidden]
>
> When replying, please edit your Subject line so it is more specific
> than "Re: Contents of Std-Discussion digest..."
>
>
> Today's Topics:
>
> 1. Re: Legality of a near-zero-cost C wrapper leveraging P0593R6
> implicit object creation (Thiago Macieira)
>
>
> ----------------------------------------------------------------------
>
> Message: 1
> Date: Thu, 24 Jul 2025 07:47:55 -0700
> From: Thiago Macieira <thiago_at_[hidden]>
> To: std-discussion_at_[hidden]
> Subject: Re: [std-discussion] Legality of a near-zero-cost C wrapper
> leveraging P0593R6 implicit object creation
> Message-ID: <1918467.eCIVR2eDZg_at_[hidden]>
> Content-Type: text/plain; charset="utf-8"
>
> On Thursday, 24 July 2025 00:00:28 Pacific Daylight Time Fran?ois
> Plumerault
> via Std-Discussion wrote:
> > namespace prv {
> > class Dummy {
> > public:
>
> Why not:
>
> struct core_dummy_s {};
> namespace prv {
> class Dummy : public core_dummy_s
> {
> public:
> ....
>
>
> In fact, why does prv::Dummy need to be a separate type from core_dummy_s?
>
> And why are you creating a C++ wrapper around a C API of a C++
> implementation?
> Can't you remove the middle abstraction?
>
> > DummyClass const& getConstDummy() {
> > core_dummy_s const* p = nullptr;
> > core_get_const_dummy(&p);
> > return *reinterpret_cast<DummyClass const*>(p);
> > }
>
> This isn't correct either: there is no object DummyClass at that location.
> This will probably work, but isn't legal.
>
> --
> Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
> Principal Engineer - Intel Platform & System Engineering
>
>
>
>
>
> ------------------------------
>
> Subject: Digest Footer
>
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>
>
> ------------------------------
>
> End of Std-Discussion Digest, Vol 76, Issue 17
> **********************************************
>
Thank you for taking the time to review my example and for raising these
points.
> In fact, why does `prv::Dummy` need to be a separate type from
`core_dummy_s`?
> And why are you creating a C++ wrapper around a C API of a C++
implementation?
> Can't you remove the middle abstraction?
My original intent was to mimic a real‑world scenario in which a C++
library exposes a pure‑C ABI that is, in turn, used by another C++ library.
As you correctly note, that extra layer is not strictly necessary for
illustrating the question, so I have condensed the code to keep only the
elements relevant to the implicit‑lifetime discussion.
Following the comment from Jens Maurer, I also added the static_assert to
ensure that the storage is big enough for the wrapper classes.
Please see the updated example below:
```cpp
//
-----------------------------------------------------------------------------
// Internal implementation
//
-----------------------------------------------------------------------------
#include <cstdio>
struct core_dummy_s {
unsigned char pubStorage[1];
};
void doStuff(core_dummy_s* dummy) { std::puts("doStuff()"); }
void doConstStuff(const core_dummy_s* dummy) { std::puts("doStuff()
const"); }
inline core_dummy_s* getCoreDummy() // single instance
{
static core_dummy_s d{};
return &d;
}
extern "C" {
void core_get_dummy(unsigned char** out)
{
*out = &getCoreDummy()->pubStorage[0];
}
void core_get_const_dummy(const unsigned char** out)
{
*out = &getCoreDummy()->pubStorage[0];
}
void core_const_dummy_do_stuff(const unsigned char* in)
{
doConstStuff(reinterpret_cast<const core_dummy_s*>(in)); //
pointer‑interconvertible
}
void core_dummy_do_stuff(unsigned char* in)
{
doStuff(reinterpret_cast<core_dummy_s*>(in)); //
pointer‑interconvertible
}
}
//
-----------------------------------------------------------------------------
// Public interface
//
-----------------------------------------------------------------------------
namespace pub {
class DummyClass { // implicit‑lifetime type,
size/alignment 1
protected:
DummyClass() = default;
public:
void doStuff() const { core_const_dummy_do_stuff(reinterpret_cast<const
unsigned char*>(this)); }
void doStuff() { core_dummy_do_stuff(reinterpret_cast<unsigned
char*>(this)); }
};
inline const DummyClass& getConstDummy()
{
const unsigned char* p = nullptr;
core_get_const_dummy(&p);
return *reinterpret_cast<const DummyClass*>(p);
}
inline DummyClass& getDummy()
{
unsigned char* p = nullptr;
core_get_dummy(&p);
return *reinterpret_cast<DummyClass*>(p);
}
// A second implicit‑lifetime type derived from the first
class DummyClass2 : public DummyClass {
private:
DummyClass2() = default;
public:
void doMoreStuff() const {
core_const_dummy_do_stuff(reinterpret_cast<const unsigned char*>(this)); }
void doMoreStuff() {
core_dummy_do_stuff(reinterpret_cast<unsigned char*>(this)); }
};
inline const DummyClass2& getConstDummy2()
{
const unsigned char* p = nullptr;
core_get_const_dummy(&p);
return *reinterpret_cast<const DummyClass2*>(p);
}
} // namespace pub
static_assert(sizeof(pub::DummyClass) == 1, "Unsupported platform");
static_assert(sizeof(pub::DummyClass2) == 1, "Unsupported platform");
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)
}
```
> There is no `DummyClass` object at that location.
> This will probably work, but isn't legal.
Could you clarify why the lifetime‑implying rules in [basic.life] (6.7.2/10
and /13) do not let us treat the storage inside `core_dummy_s` as
implicitly containing a `DummyClass2`, and therefore also a `DummyClass`,
once that storage begins its lifetime?
>From my reading, those paragraphs allow an operation that "begins the
lifetime of an array of `unsigned char`" (here, the static initialization
of `pubStorage`) to implicitly create any implicit‑lifetime objects that
would give the program defined behaviour. This is what led me to believe
that a `DummyClass2`, and hence a base‑subobject `DummyClass`, would be
considered to exist at that address.
I would greatly appreciate any insight (especially a specific clause)
showing why this interpretation is incorrect or correct.
Thank you again for your time.
Best regards,
François Plumerault
Le ven. 25 juil. 2025 à 14:00, <std-discussion-request_at_[hidden]> a
écrit :
> Send Std-Discussion mailing list submissions to
> std-discussion_at_[hidden]
>
> To subscribe or unsubscribe via the World Wide Web, visit
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
> or, via email, send a message with subject or body 'help' to
> std-discussion-request_at_[hidden]
>
> You can reach the person managing the list at
> std-discussion-owner_at_[hidden]
>
> When replying, please edit your Subject line so it is more specific
> than "Re: Contents of Std-Discussion digest..."
>
>
> Today's Topics:
>
> 1. Re: Legality of a near-zero-cost C wrapper leveraging P0593R6
> implicit object creation (Thiago Macieira)
>
>
> ----------------------------------------------------------------------
>
> Message: 1
> Date: Thu, 24 Jul 2025 07:47:55 -0700
> From: Thiago Macieira <thiago_at_[hidden]>
> To: std-discussion_at_[hidden]
> Subject: Re: [std-discussion] Legality of a near-zero-cost C wrapper
> leveraging P0593R6 implicit object creation
> Message-ID: <1918467.eCIVR2eDZg_at_[hidden]>
> Content-Type: text/plain; charset="utf-8"
>
> On Thursday, 24 July 2025 00:00:28 Pacific Daylight Time Fran?ois
> Plumerault
> via Std-Discussion wrote:
> > namespace prv {
> > class Dummy {
> > public:
>
> Why not:
>
> struct core_dummy_s {};
> namespace prv {
> class Dummy : public core_dummy_s
> {
> public:
> ....
>
>
> In fact, why does prv::Dummy need to be a separate type from core_dummy_s?
>
> And why are you creating a C++ wrapper around a C API of a C++
> implementation?
> Can't you remove the middle abstraction?
>
> > DummyClass const& getConstDummy() {
> > core_dummy_s const* p = nullptr;
> > core_get_const_dummy(&p);
> > return *reinterpret_cast<DummyClass const*>(p);
> > }
>
> This isn't correct either: there is no object DummyClass at that location.
> This will probably work, but isn't legal.
>
> --
> Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
> Principal Engineer - Intel Platform & System Engineering
>
>
>
>
>
> ------------------------------
>
> Subject: Digest Footer
>
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>
>
> ------------------------------
>
> End of Std-Discussion Digest, Vol 76, Issue 17
> **********************************************
>
Received on 2025-07-25 17:37:33