C++ Logo

std-proposals

Advanced search

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

From: Rhidian De Wit <rhidiandewit_at_[hidden]>
Date: Fri, 19 Jun 2026 20:44:02 +0200
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

Received on 2026-06-19 18:44:17