C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Translation-unit-local functions that access private class fields

From: Sebastian Wittmeier <wittmeier_at_[hidden]>
Date: Sun, 21 Jun 2026 08:59:21 +0200
Is it (should it be) possible to declare (and not at the same time, but later, define) PEMs?   If they are meant to be used in the same translation unit, there are less reasons for a declaration-only.   But still there could be reasons?   -----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] <mailto:std-proposals_at_[hidden]> >: On Thu, 28 May 2026, 16:51 Máté Ték via Std-Proposals, <std-proposals_at_[hidden] <mailto: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] <mailto: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 07:03:09