Date: Mon, 22 Jun 2026 01:47:25 +0200
Hi!
I also had time to think, and I'd like to share what I found.
I'll give you my train of thought I had some days ago, some of which I will
backtrack, because I discovered new theory since.
Please, do not take this too seriously.
I'm not trying to say "my approach is good, your approach is bad", nothing
like that.
I just want to say "I explored in this direction, and look what I found".
> I've been thinking about the feature here and I think that re-opening
the class scope is out-of-scope for this problem.
I don't think we can avoid "re-opening the class scope".
It's exactly what we want to do! Even if we only add new *private* members
for a start.
I suppose that "re-opening the class scope" means adding a new entity to
the "namespace of the class", i.e.
fully_qualified_class_name::new_member
Mathematically speaking (and syntactically in C++), we can think of a class
as a kind of namespace, with special rules.
Until now, it was forbidden to add a new entity to this namespace outside
the class definition.
But a PEM is exactly that; a late addition to this namespace, i.e. we just
"re-opened" the "class scope".
So really we are asking for trouble here, and we should not be afraid to
admit it.
Compare this with "regular" namespaces:
- We're allowed to add new entities to them freely at any time (more or
less).
- Not all TUs or all contexts need to see *all* entities that exist in a
namespace in the whole C++ program ("partial discovery").
Why not allow the same for class members?
So I embraced this concept, and I wanted to see how far I can take
it, without breaking the language of course.
We should be able to add new members without the permission of the class.
(**I'm going to backtrack this**)
We already know a couple caveats:
- Only non-ABI-relevant members can be added (no virtual functions, no
non-static data members, etc.)
- Semantics of the class must be preserved, i.e. no special members like
copy ctor, etc.
- Encapsulation safety must be preserved.
- Adding members directly to the "namespace" fully_qualified_class_name:: is
problematic and can lead to ODR issues or unexpected bugs.
The last point can be circumvented if the new members *must* live in a
dedicated "in-class namespace", e.g.
fully_qualified_class_name::ext::member
I call these new in-class namespaces "facets". It's the best term I could
come up with instead of "extensions".
I don't think "extension" is the right term. I don't think it captures the
essence of what we are trying to do.
We are trying to split the interface of a class, on purpose, into different
logical partitions, e.g. public/ABI interface vs implementation.
Hiding some of it should not be the sole fruitage of this new language
feature, more like a "deliberate byproduct".
It should enable us to do so, but should not be limited to just that.
Also, "extension" implies (to me) that the thing we are extending is
already complete in some sense, i.e. without the extension, the remainder
should still be a valid, self-contained entity, just with less capabilities.
However, if we take away the "private implementation extension" of the
class, then it ceases to function, because it was an integral part of the
class.
Hence I looked for a different term.
I identified a couple key thoughts to preserve encapsulation safety:
- We can introduce new members that have unconditional private access to a
class, but only if they themselves are only usable in contexts that already
have private access to the class.
- Such entities with private access *must not* be allowed to declare
friends, only the base class should be able to grant friend access to third
parties.
- Similarly, if we want to introduce new members that are usable by anyone
(public extension), then they must only be able to operate with the public
interface of the class, unless they are granted friend access by the class
itself.
>From these, I derived the following rudimentary rules for these "facets":
1. One facet is associated with exactly one class. A class can have
multiple associated facets.
2. The base class does not need to know about all associated facets.
External facets can be attached without modifying the class definition.
3. Facets must have a unique name (cannot be unnamed) that is different
from the name of the class and all parent classes.
4. The facet itself acts as a sort of "namespace" within the class, the
entities (e.g. member functions) of the facet shall appear to be in the
"namespace"
fully_qualified_class_name::facet_name
5. No "ADL for facet members"; to use them, they must be referred to
with their fully qualified name, except the class name part can be omitted
if it is unambiguous, e.g. this->Foo() always invokes the "non-facetal"
member function Foo() whereas the "facetal" member function is invoked
with this->facet::Foo().
(It's not mandatory to use the explicit this-> notation, I just used it
for clarity.)
6. Facets must be assigned a single access specifier (public, protected,
private), that controls both the access to the class members within the
facet, and the accessibility of the facet members themselves, e.g. the
members of a private facet shall behave as if they were private members of
the class, with private access to the class.
7. Facets can only contain entities that would make sense as members of
a class, but do not affect class layout, i.e. no virtual functions, no
non-static data members, and do not affect class semantics, i.e. no special
member functions like copy ctor, etc.
8. Also, no friend declarations inside facets. Only the base class
should be able to grant friend access to a third party.
9. Facets can be predeclared and befriended, possibly by the base class
itself.
10. Facets (more precisely, its members) can have different linkage than
their base class.
11. Facets can be defined in-class, in which case they have private
access to the base class, regardless of their own access specifier.
Here are some possible use cases that we get from these rules:
Example 1: Hiding private implementation details (the original proposal)
//////// C.hpp ////////
class C {
int m_secret;
public:
void Foo();
};
//////// C.cpp ////////
#include "C.hpp"
namespace {
private facet C::impl {
void Foo() { ++m_secret; }
};
}
void C::Foo() { impl::Foo(); }
Or we could hide the entire class layout (!!) with a forward-defined public
facet, which IMO is even more amazing.
Example 2
//////// C_api.hpp ////////
class C;
public facet C::api {
void Foo(int); // Defined in C.cpp where it has private access
};
//////// SomeSourceFile.cpp ////////
#include "C_api.hpp"
void Use(C& c) { c.api::Foo(5); }
//////// C.hpp ////////
#include "C_api.hpp"
class C {
int m_secret = 0;
friend C::api; // Syntax debatable. "friend facet api;"?
};
//////// C.cpp ////////
#include "C.hpp"
void C::api::Foo(int i) { m_secret += i; }
Example 3: Restrict the scope of friend access to a facet.
It's always a good idea to reduce the scope of friend access. Facets would
allow more granularity.
//////// C.hpp ////////
class D;
private facet D::authorized;
class C {
int m_secret;
friend D::authorized;
};
//////// D.hpp ////////
#include "C.hpp"
class D {
facet authorized {
static int GetSecret(const C& c) { return c.m_secret; }
}
};
Example 4: Using facets to refactor an unwieldy overgrown class.
We've all been there.
This may be an intermediate step when untangling the class, but definitely
better than leaving it unorganised.
class Unwieldy {
facet event {...} // Members related to event handling
facet thread {...} // Members related to threading
...
};
Example 5A: Defining entirely new operations for existing classes, using
only their public API
template<class T, class A>
public facet std::vector<T, A>::ptr {
// Public facet -> can only use public interface of vector
template<typename Self>
auto begin(this Self&&) { return data(); }
template<typename Self>
auto end(this Self&&) { return ptr::begin() + size(); } // must prefix
even here
template<typename Self>
auto at(this Self&&, size_type pos) { return &(at(pos)); } // 'at()' =
true member
};
// Usage:
std::vector<int> v = { /* ... */ };
int* fifth = v.ptr::at(5);
Example 5B
template<class T, class A>
public facet std::vector<T, A>::heap requires(...) { // Comparable T
void make_heap() { std::make_heap(begin(), end()); }
void push(const T& x) { push_back(x); std::push_heap(begin(), end()); }
void pop() { /* ... */ }
const_reference top() const { /* ... */ }
};
// Usage:
std::vector<int> v = { /* ... */ };
v.heap::make_heap();
int top = v.heap::top();
v.heap::pop();
Here comes the backtracking.
As much as I'd love to be able to do something like examples 5A/5B, there's
an issue here:
What if I used a static library in my code, which also defined its own
vector heap facet, and I have one too?
This would be an amazing "interface glue" feature, but name collision
becomes an issue again.
We could try to find a solution to this, e.g. the new members *do not* live
in the "namespace of the class", but somewhere else.
E.g. we could have
namespace myOrg {
public facet vector<...>::heap { ... };
}
and the new "members" would appear as having class member type but in the
myOrg namespace:
namespace myOrg {
void (std::vector<T, A>::* make_heap)();
}
But this is a whole new level of scary, and I did not go further.
The second backtrack is:
I made an arbitrary choice, and said "we should be able to add new members
without the permission of the class".
I later realized, this is a very important aspect.
> Can public interface extensions only access public members (with an
implicit this pointer)
> or are they themselves publicly accessible?
> From your post both seems to be true. And it is mixing two concepts.
If we don't need the permission of the class to create new facets, then a
public extension (facet) must only have public access to the class,
otherwise we break encapsulation.
So yes, I am mixing the two concepts, because they are not independent,
and it's the only way to not break the language.
However, if we require the class' permission to define facets anyhow, then
all facets can have private access to the class, which is a dramatic
change, and we are no longer mixing the two concepts.
So something like
class C { facet impl; ... }; // Facet *must* be declared by the class itself
It is still possible to "forward define" an API for a class and hide its
memory layout, like in Example 2, if the rule is the following:
A facet interface can be declared without seeing the class definition,
however, at the point of definition of the facet members, the class must be
seen and the facet must be "authorized" by the class.
So something like this should work:
//////// C_api.hpp ////////
class C;
facet C::api { // Notice, no access specifier is needed here
void Foo(int); // Cannot be defined inline, would be a compilation error
};
//////// C.hpp ////////
#include "C_api.hpp"
class C {
int m_secret = 0;
facet api; // Authorizes the facet
};
//////// C.cpp ////////
#include "C.hpp"
void C::api::Foo(int i) { m_secret += i; } // OK, class is seen,
authorization OK
Due to these, it also becomes possible to then have facet members with
"mixed access specifiers", e.g.
facet C::impl {
public:
void Foo();
private:
void Bar();
};
But this comes down to whether we require the base class' permission or not!
This final approach I showed, seems safer and more conservative.
We can no longer attach new arbitrary facets to an existing class.
It becomes more of a code organisational tool only for the author of the
class, which may or may not be a good thing.
> I think the main problem with reopening the class scope is that suddenly
you might be able to add a new function to library code where it was not
intended for the user to be able to add library code, especially not new
code that can access the library's internals.
Then I suppose you would endorse this last approach.
Maybe we can take the best parts of all of our ideas, and knead them into
something even better?
Thoughts?
I will try to reply to your latest mails, I see there are many.
Sincerely,
Matthew
On Fri, 19 Jun 2026 at 23:01, Rhidian De Wit via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> Assuming the functions are inlined I would think?
> Otherwise we would get ODR violations if I'm not mistaken. Consider the
> following:
> // Foo.h
> class Foo {
> private:
> int m_var;
>
> public:
> Foo();
> };
>
> private impl Foo {
> void Print() {
> std::cout << m_var;
> }
> }
>
> // Foo.cpp
> Foo::Foo() {
> m_var = 10;
> Print(); // First Translation Unit with 'Print()' defined. OK
> }
>
> // Bar.cpp
> Bar::Bar(Foo & fooInstance) {
> fooInstance.Print(); // 2nd Translation Unit with 'Print()' defined. ODR
> Violation!
> }
>
> Inlining Foo should fix the ODR violation, but is that something that
> we'd even want to allow? PEMs are *private* extension methods, if we
> allow them to be shared across TU's they wouldn't be private anymore and
> we'd get closer again to the discussion of reopening the class scope.
> I think the principle of PEMs should be to not be shared at all. If you
> want to share helper methods, you can always just define a free function or
> a public (static) member function and not worry about extra ODR, function
> overloading, ... rules
>
> I do think that reopening the class scope holds some merit to it, but I
> think PEMs are a good starting off point for such a thing:
> We can test out privately reopening the class scope and can then further
> extend it to *public* extension methods that *can* be shared among TUs.
>
> Best,
>
> Rhidian
>
> Op vr 19 jun 2026 om 20:50 schreef Sebastian Wittmeier via Std-Proposals <
> std-proposals_at_[hidden]>:
>
>> I assume, if you want to share the PEM between translation units after
>> all,
>>
>> you can just put the PEM definition inside a (header) file and include it
>> from every translation unit using it.
>>
>>
>> -----Ursprüngliche Nachricht-----
>> *Von:* Rhidian De Wit via Std-Proposals <std-proposals_at_[hidden]>
>> *Gesendet:* Fr 19.06.2026 20:44
>> *Betreff:* Re: [std-proposals] Translation-unit-local functions that
>> access private class fields
>> *An:* std-proposals_at_[hidden];
>> *CC:* Rhidian De Wit <rhidiandewit_at_[hidden]>;
>> Hi all,
>>
>> I've been thinking about the feature here and I think that re-opening the
>> class scope is out-of-scope for this problem.
>> I think the main problem with reopening the class scope is that suddenly
>> you might be able to add a new function to library code where it was not
>> intended for the user to be able to add library code, especially not new
>> code that can access the library's internals.
>>
>> Therefore, I think the PEMs should remain local to a translation unit to
>> avoid ODR violations and to avoid breaking encapsulation of external
>> libraries.
>>
>> I do think something akin to Rust's impl blocks would be nice, but I
>> think that's where PEMs come in. We could define a PEM as:
>> // Foo.h
>> class Foo {
>> private:
>> int m_var;
>>
>> public:
>> Foo();
>>
>> void RecalculateVar();
>> };
>>
>> // Foo.cpp
>> private impl Foo {
>> int CalculateNewVar() {
>> int newVar = // Complex calculation goes here...
>> return m_var + newVar;
>> }
>> }
>>
>> Foo::Foo()
>> : m_var(0)
>> {
>> m_var = CalculateNewVar();
>> }
>>
>> void Foo::RecalculateVar() {
>> // Gets called by some thread every X minutes
>> m_var = CalculateNewVar();
>> }
>>
>> I think that's a nice start to PEMs. We can move complexity and symbols
>> out of the header file yet allow functions access to private member
>> variables and prevent any ODR violations in the process.
>> I would also then mandate that functions in PEMs cannot overload
>> non-static member functions because it would greatly improve teachability
>> rather than explaining why your best fit overload defined in a PEM does not
>> get selected for overload resolution.
>>
>> Best,
>>
>> Rhidian
>>
>> Op vr 29 mei 2026 om 06:05 schreef Mital Ashok via Std-Proposals <
>> std-proposals_at_[hidden]>:
>>
>> On Thu, 28 May 2026, 16:51 Máté Ték via Std-Proposals, <
>> std-proposals_at_[hidden]> wrote:
>>
>>
>> The real issue with classic PEMs IMO is that they live in the same "name
>> space" as the "first-class class members".
>> This is what generates the friction with overload resolution/ODR.
>> So I thought, let's give them a "name space" within the class they're
>> extending!
>> After some fooling around with various syntaxes, here's my strongest
>> candidate:
>>
>> /// MyClass.hpp
>> class MyClass {
>> public:
>> void Foo(int);
>> void Bar(int);
>> private:
>> int mySecret;
>> };
>>
>> /// MyClass.cpp
>> namespace {
>> private extension MyClass::impl {
>> void FooBar(int i) { mySecret += i; }
>> // virtual void Gaz(); ERROR: extension member function cannot
>> be virtual
>> // int otherSecret; ERROR: extension cannot have non-static
>> data members
>> static int otherSecret; // OK, why not?
>> };
>> // I could have defined FooBar out-of-line like so:
>> void MyClass::impl::FooBar(int i) { mySecret += i; }
>> }
>> void MyClass::Foo(int i) { impl::FooBar(i + 1); }
>> void MyClass::Bar(int i) { impl::FooBar(i * 2); }
>> // static void SomeFunction(MyClass& x) { x.impl::FooBar(42); } ERROR:
>> class interface extension MyClass::impl is private
>>
>> Thus we eliminated the overload resolution/ODR concerns.
>> It is now very clear that PEMs are not "first-class" members of the class.
>> The interface of the class, and therefore its "identity" is undisputed
>> and unfractured.
>>
>>
>> A similar thing can be implemented with existing language features:
>>
>> // header
>> class X {
>> private:
>> // ...
>> struct impl;
>> friend impl;
>> public:
>> int public_interface();
>> };
>>
>> // source
>> struct X::impl {
>> static int helper(X& self) {
>> // ...
>> }
>> };
>> int X::public_interface() {
>> return impl::helper(*this);
>> }
>>
>>
>> This loses the easier syntax of an implicit object argument and has
>> external linkage (though you can work around that with `namespace detail {
>> namespace { struct X_helper; } } ` and `friend detail::X_helper;`), but is
>> implementable today without exposing too much in the header.
>>
>>
>> Encapsulation is not broken.
>> Interface extensions naturally follow the good ol' class syntax.
>>
>>
>> Encapsulation/access control does seem broken. This could allow private
>> member access in any translation unit by just defining an extension. This
>> could be fixed by requiring a declaration of the extension but that is
>> closer to something like Java interfaces as opposed to Rust impl/traits
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
>>
>>
>> --
>> Rhidian De Wit
>> Software Engineer - Barco
>>
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
>
>
> --
> Rhidian De Wit
> Software Engineer - Barco
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
I also had time to think, and I'd like to share what I found.
I'll give you my train of thought I had some days ago, some of which I will
backtrack, because I discovered new theory since.
Please, do not take this too seriously.
I'm not trying to say "my approach is good, your approach is bad", nothing
like that.
I just want to say "I explored in this direction, and look what I found".
> I've been thinking about the feature here and I think that re-opening
the class scope is out-of-scope for this problem.
I don't think we can avoid "re-opening the class scope".
It's exactly what we want to do! Even if we only add new *private* members
for a start.
I suppose that "re-opening the class scope" means adding a new entity to
the "namespace of the class", i.e.
fully_qualified_class_name::new_member
Mathematically speaking (and syntactically in C++), we can think of a class
as a kind of namespace, with special rules.
Until now, it was forbidden to add a new entity to this namespace outside
the class definition.
But a PEM is exactly that; a late addition to this namespace, i.e. we just
"re-opened" the "class scope".
So really we are asking for trouble here, and we should not be afraid to
admit it.
Compare this with "regular" namespaces:
- We're allowed to add new entities to them freely at any time (more or
less).
- Not all TUs or all contexts need to see *all* entities that exist in a
namespace in the whole C++ program ("partial discovery").
Why not allow the same for class members?
So I embraced this concept, and I wanted to see how far I can take
it, without breaking the language of course.
We should be able to add new members without the permission of the class.
(**I'm going to backtrack this**)
We already know a couple caveats:
- Only non-ABI-relevant members can be added (no virtual functions, no
non-static data members, etc.)
- Semantics of the class must be preserved, i.e. no special members like
copy ctor, etc.
- Encapsulation safety must be preserved.
- Adding members directly to the "namespace" fully_qualified_class_name:: is
problematic and can lead to ODR issues or unexpected bugs.
The last point can be circumvented if the new members *must* live in a
dedicated "in-class namespace", e.g.
fully_qualified_class_name::ext::member
I call these new in-class namespaces "facets". It's the best term I could
come up with instead of "extensions".
I don't think "extension" is the right term. I don't think it captures the
essence of what we are trying to do.
We are trying to split the interface of a class, on purpose, into different
logical partitions, e.g. public/ABI interface vs implementation.
Hiding some of it should not be the sole fruitage of this new language
feature, more like a "deliberate byproduct".
It should enable us to do so, but should not be limited to just that.
Also, "extension" implies (to me) that the thing we are extending is
already complete in some sense, i.e. without the extension, the remainder
should still be a valid, self-contained entity, just with less capabilities.
However, if we take away the "private implementation extension" of the
class, then it ceases to function, because it was an integral part of the
class.
Hence I looked for a different term.
I identified a couple key thoughts to preserve encapsulation safety:
- We can introduce new members that have unconditional private access to a
class, but only if they themselves are only usable in contexts that already
have private access to the class.
- Such entities with private access *must not* be allowed to declare
friends, only the base class should be able to grant friend access to third
parties.
- Similarly, if we want to introduce new members that are usable by anyone
(public extension), then they must only be able to operate with the public
interface of the class, unless they are granted friend access by the class
itself.
>From these, I derived the following rudimentary rules for these "facets":
1. One facet is associated with exactly one class. A class can have
multiple associated facets.
2. The base class does not need to know about all associated facets.
External facets can be attached without modifying the class definition.
3. Facets must have a unique name (cannot be unnamed) that is different
from the name of the class and all parent classes.
4. The facet itself acts as a sort of "namespace" within the class, the
entities (e.g. member functions) of the facet shall appear to be in the
"namespace"
fully_qualified_class_name::facet_name
5. No "ADL for facet members"; to use them, they must be referred to
with their fully qualified name, except the class name part can be omitted
if it is unambiguous, e.g. this->Foo() always invokes the "non-facetal"
member function Foo() whereas the "facetal" member function is invoked
with this->facet::Foo().
(It's not mandatory to use the explicit this-> notation, I just used it
for clarity.)
6. Facets must be assigned a single access specifier (public, protected,
private), that controls both the access to the class members within the
facet, and the accessibility of the facet members themselves, e.g. the
members of a private facet shall behave as if they were private members of
the class, with private access to the class.
7. Facets can only contain entities that would make sense as members of
a class, but do not affect class layout, i.e. no virtual functions, no
non-static data members, and do not affect class semantics, i.e. no special
member functions like copy ctor, etc.
8. Also, no friend declarations inside facets. Only the base class
should be able to grant friend access to a third party.
9. Facets can be predeclared and befriended, possibly by the base class
itself.
10. Facets (more precisely, its members) can have different linkage than
their base class.
11. Facets can be defined in-class, in which case they have private
access to the base class, regardless of their own access specifier.
Here are some possible use cases that we get from these rules:
Example 1: Hiding private implementation details (the original proposal)
//////// C.hpp ////////
class C {
int m_secret;
public:
void Foo();
};
//////// C.cpp ////////
#include "C.hpp"
namespace {
private facet C::impl {
void Foo() { ++m_secret; }
};
}
void C::Foo() { impl::Foo(); }
Or we could hide the entire class layout (!!) with a forward-defined public
facet, which IMO is even more amazing.
Example 2
//////// C_api.hpp ////////
class C;
public facet C::api {
void Foo(int); // Defined in C.cpp where it has private access
};
//////// SomeSourceFile.cpp ////////
#include "C_api.hpp"
void Use(C& c) { c.api::Foo(5); }
//////// C.hpp ////////
#include "C_api.hpp"
class C {
int m_secret = 0;
friend C::api; // Syntax debatable. "friend facet api;"?
};
//////// C.cpp ////////
#include "C.hpp"
void C::api::Foo(int i) { m_secret += i; }
Example 3: Restrict the scope of friend access to a facet.
It's always a good idea to reduce the scope of friend access. Facets would
allow more granularity.
//////// C.hpp ////////
class D;
private facet D::authorized;
class C {
int m_secret;
friend D::authorized;
};
//////// D.hpp ////////
#include "C.hpp"
class D {
facet authorized {
static int GetSecret(const C& c) { return c.m_secret; }
}
};
Example 4: Using facets to refactor an unwieldy overgrown class.
We've all been there.
This may be an intermediate step when untangling the class, but definitely
better than leaving it unorganised.
class Unwieldy {
facet event {...} // Members related to event handling
facet thread {...} // Members related to threading
...
};
Example 5A: Defining entirely new operations for existing classes, using
only their public API
template<class T, class A>
public facet std::vector<T, A>::ptr {
// Public facet -> can only use public interface of vector
template<typename Self>
auto begin(this Self&&) { return data(); }
template<typename Self>
auto end(this Self&&) { return ptr::begin() + size(); } // must prefix
even here
template<typename Self>
auto at(this Self&&, size_type pos) { return &(at(pos)); } // 'at()' =
true member
};
// Usage:
std::vector<int> v = { /* ... */ };
int* fifth = v.ptr::at(5);
Example 5B
template<class T, class A>
public facet std::vector<T, A>::heap requires(...) { // Comparable T
void make_heap() { std::make_heap(begin(), end()); }
void push(const T& x) { push_back(x); std::push_heap(begin(), end()); }
void pop() { /* ... */ }
const_reference top() const { /* ... */ }
};
// Usage:
std::vector<int> v = { /* ... */ };
v.heap::make_heap();
int top = v.heap::top();
v.heap::pop();
Here comes the backtracking.
As much as I'd love to be able to do something like examples 5A/5B, there's
an issue here:
What if I used a static library in my code, which also defined its own
vector heap facet, and I have one too?
This would be an amazing "interface glue" feature, but name collision
becomes an issue again.
We could try to find a solution to this, e.g. the new members *do not* live
in the "namespace of the class", but somewhere else.
E.g. we could have
namespace myOrg {
public facet vector<...>::heap { ... };
}
and the new "members" would appear as having class member type but in the
myOrg namespace:
namespace myOrg {
void (std::vector<T, A>::* make_heap)();
}
But this is a whole new level of scary, and I did not go further.
The second backtrack is:
I made an arbitrary choice, and said "we should be able to add new members
without the permission of the class".
I later realized, this is a very important aspect.
> Can public interface extensions only access public members (with an
implicit this pointer)
> or are they themselves publicly accessible?
> From your post both seems to be true. And it is mixing two concepts.
If we don't need the permission of the class to create new facets, then a
public extension (facet) must only have public access to the class,
otherwise we break encapsulation.
So yes, I am mixing the two concepts, because they are not independent,
and it's the only way to not break the language.
However, if we require the class' permission to define facets anyhow, then
all facets can have private access to the class, which is a dramatic
change, and we are no longer mixing the two concepts.
So something like
class C { facet impl; ... }; // Facet *must* be declared by the class itself
It is still possible to "forward define" an API for a class and hide its
memory layout, like in Example 2, if the rule is the following:
A facet interface can be declared without seeing the class definition,
however, at the point of definition of the facet members, the class must be
seen and the facet must be "authorized" by the class.
So something like this should work:
//////// C_api.hpp ////////
class C;
facet C::api { // Notice, no access specifier is needed here
void Foo(int); // Cannot be defined inline, would be a compilation error
};
//////// C.hpp ////////
#include "C_api.hpp"
class C {
int m_secret = 0;
facet api; // Authorizes the facet
};
//////// C.cpp ////////
#include "C.hpp"
void C::api::Foo(int i) { m_secret += i; } // OK, class is seen,
authorization OK
Due to these, it also becomes possible to then have facet members with
"mixed access specifiers", e.g.
facet C::impl {
public:
void Foo();
private:
void Bar();
};
But this comes down to whether we require the base class' permission or not!
This final approach I showed, seems safer and more conservative.
We can no longer attach new arbitrary facets to an existing class.
It becomes more of a code organisational tool only for the author of the
class, which may or may not be a good thing.
> I think the main problem with reopening the class scope is that suddenly
you might be able to add a new function to library code where it was not
intended for the user to be able to add library code, especially not new
code that can access the library's internals.
Then I suppose you would endorse this last approach.
Maybe we can take the best parts of all of our ideas, and knead them into
something even better?
Thoughts?
I will try to reply to your latest mails, I see there are many.
Sincerely,
Matthew
On Fri, 19 Jun 2026 at 23:01, Rhidian De Wit via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> Assuming the functions are inlined I would think?
> Otherwise we would get ODR violations if I'm not mistaken. Consider the
> following:
> // Foo.h
> class Foo {
> private:
> int m_var;
>
> public:
> Foo();
> };
>
> private impl Foo {
> void Print() {
> std::cout << m_var;
> }
> }
>
> // Foo.cpp
> Foo::Foo() {
> m_var = 10;
> Print(); // First Translation Unit with 'Print()' defined. OK
> }
>
> // Bar.cpp
> Bar::Bar(Foo & fooInstance) {
> fooInstance.Print(); // 2nd Translation Unit with 'Print()' defined. ODR
> Violation!
> }
>
> Inlining Foo should fix the ODR violation, but is that something that
> we'd even want to allow? PEMs are *private* extension methods, if we
> allow them to be shared across TU's they wouldn't be private anymore and
> we'd get closer again to the discussion of reopening the class scope.
> I think the principle of PEMs should be to not be shared at all. If you
> want to share helper methods, you can always just define a free function or
> a public (static) member function and not worry about extra ODR, function
> overloading, ... rules
>
> I do think that reopening the class scope holds some merit to it, but I
> think PEMs are a good starting off point for such a thing:
> We can test out privately reopening the class scope and can then further
> extend it to *public* extension methods that *can* be shared among TUs.
>
> Best,
>
> Rhidian
>
> Op vr 19 jun 2026 om 20:50 schreef Sebastian Wittmeier via Std-Proposals <
> std-proposals_at_[hidden]>:
>
>> I assume, if you want to share the PEM between translation units after
>> all,
>>
>> you can just put the PEM definition inside a (header) file and include it
>> from every translation unit using it.
>>
>>
>> -----Ursprüngliche Nachricht-----
>> *Von:* Rhidian De Wit via Std-Proposals <std-proposals_at_[hidden]>
>> *Gesendet:* Fr 19.06.2026 20:44
>> *Betreff:* Re: [std-proposals] Translation-unit-local functions that
>> access private class fields
>> *An:* std-proposals_at_[hidden];
>> *CC:* Rhidian De Wit <rhidiandewit_at_[hidden]>;
>> Hi all,
>>
>> I've been thinking about the feature here and I think that re-opening the
>> class scope is out-of-scope for this problem.
>> I think the main problem with reopening the class scope is that suddenly
>> you might be able to add a new function to library code where it was not
>> intended for the user to be able to add library code, especially not new
>> code that can access the library's internals.
>>
>> Therefore, I think the PEMs should remain local to a translation unit to
>> avoid ODR violations and to avoid breaking encapsulation of external
>> libraries.
>>
>> I do think something akin to Rust's impl blocks would be nice, but I
>> think that's where PEMs come in. We could define a PEM as:
>> // Foo.h
>> class Foo {
>> private:
>> int m_var;
>>
>> public:
>> Foo();
>>
>> void RecalculateVar();
>> };
>>
>> // Foo.cpp
>> private impl Foo {
>> int CalculateNewVar() {
>> int newVar = // Complex calculation goes here...
>> return m_var + newVar;
>> }
>> }
>>
>> Foo::Foo()
>> : m_var(0)
>> {
>> m_var = CalculateNewVar();
>> }
>>
>> void Foo::RecalculateVar() {
>> // Gets called by some thread every X minutes
>> m_var = CalculateNewVar();
>> }
>>
>> I think that's a nice start to PEMs. We can move complexity and symbols
>> out of the header file yet allow functions access to private member
>> variables and prevent any ODR violations in the process.
>> I would also then mandate that functions in PEMs cannot overload
>> non-static member functions because it would greatly improve teachability
>> rather than explaining why your best fit overload defined in a PEM does not
>> get selected for overload resolution.
>>
>> Best,
>>
>> Rhidian
>>
>> Op vr 29 mei 2026 om 06:05 schreef Mital Ashok via Std-Proposals <
>> std-proposals_at_[hidden]>:
>>
>> On Thu, 28 May 2026, 16:51 Máté Ték via Std-Proposals, <
>> std-proposals_at_[hidden]> wrote:
>>
>>
>> The real issue with classic PEMs IMO is that they live in the same "name
>> space" as the "first-class class members".
>> This is what generates the friction with overload resolution/ODR.
>> So I thought, let's give them a "name space" within the class they're
>> extending!
>> After some fooling around with various syntaxes, here's my strongest
>> candidate:
>>
>> /// MyClass.hpp
>> class MyClass {
>> public:
>> void Foo(int);
>> void Bar(int);
>> private:
>> int mySecret;
>> };
>>
>> /// MyClass.cpp
>> namespace {
>> private extension MyClass::impl {
>> void FooBar(int i) { mySecret += i; }
>> // virtual void Gaz(); ERROR: extension member function cannot
>> be virtual
>> // int otherSecret; ERROR: extension cannot have non-static
>> data members
>> static int otherSecret; // OK, why not?
>> };
>> // I could have defined FooBar out-of-line like so:
>> void MyClass::impl::FooBar(int i) { mySecret += i; }
>> }
>> void MyClass::Foo(int i) { impl::FooBar(i + 1); }
>> void MyClass::Bar(int i) { impl::FooBar(i * 2); }
>> // static void SomeFunction(MyClass& x) { x.impl::FooBar(42); } ERROR:
>> class interface extension MyClass::impl is private
>>
>> Thus we eliminated the overload resolution/ODR concerns.
>> It is now very clear that PEMs are not "first-class" members of the class.
>> The interface of the class, and therefore its "identity" is undisputed
>> and unfractured.
>>
>>
>> A similar thing can be implemented with existing language features:
>>
>> // header
>> class X {
>> private:
>> // ...
>> struct impl;
>> friend impl;
>> public:
>> int public_interface();
>> };
>>
>> // source
>> struct X::impl {
>> static int helper(X& self) {
>> // ...
>> }
>> };
>> int X::public_interface() {
>> return impl::helper(*this);
>> }
>>
>>
>> This loses the easier syntax of an implicit object argument and has
>> external linkage (though you can work around that with `namespace detail {
>> namespace { struct X_helper; } } ` and `friend detail::X_helper;`), but is
>> implementable today without exposing too much in the header.
>>
>>
>> Encapsulation is not broken.
>> Interface extensions naturally follow the good ol' class syntax.
>>
>>
>> Encapsulation/access control does seem broken. This could allow private
>> member access in any translation unit by just defining an extension. This
>> could be fixed by requiring a declaration of the extension but that is
>> closer to something like Java interfaces as opposed to Rust impl/traits
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
>>
>>
>> --
>> Rhidian De Wit
>> Software Engineer - Barco
>>
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
>
>
> --
> Rhidian De Wit
> Software Engineer - Barco
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
Received on 2026-06-21 23:47:39
