C++ Logo

std-proposals

Advanced search

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

From: Simon Schröder <dr.simon.schroeder_at_[hidden]>
Date: Sun, 21 Jun 2026 12:21:44 +0200


On Jun 21, 2026, at 9:03 AM, Sebastian Wittmeier via Std-Proposals <std-proposals_at_[hidden]> wrote:

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

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?


One technical reason could be for PEMs that call each other. We would need at least one of the declarations first to be found by the other.

One could certainly discuss lines of code of member functions, but they could be large in some code bases. I personally would prefer to have all PEM declarations right next to each other at the top of my implementation file (or maybe even a separate private header file).

Certainly, clean code would dictate that one class should not have that many member functions to warrant a separate private header file or that PEMs should only be about 3 lines of code. But, clean code is not part of the C++ standard. So, there are definitely “reasons” for code organization.

 

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

Received on 2026-06-21 10:22:03