C++ Logo

std-proposals

Advanced search

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

From: Steve Weinrich <weinrich.steve_at_[hidden]>
Date: Fri, 19 Jun 2026 17:23:09 -0500
Yes, currently there is no distinction between different types of input
files to the compiler. But there could be. As to the preprocessor, I
believe they have long disappeared. Yes, there is a phase of the compiler
that does this, but it is no longer a separate executable. Thus, there is
information that could be used. It just currently is not.

On Fri, Jun 19, 2026, 16:57 Sebastian Wittmeier via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> A header file is just included by the preprocessor.
>
>
>
> Currently 'nearly everything' can be inside header files.
>
>
>
> Could you rephrase, please?
>
>
>
>
> -----Ursprüngliche Nachricht-----
> *Von:* Steve Weinrich via Std-Proposals <std-proposals_at_[hidden]>
> *Gesendet:* Fr 19.06.2026 23:21
> *Betreff:* Re: [std-proposals] Translation-unit-local functions that
> access private class fields
> *An:* std-proposals_at_[hidden];
> *CC:* weinrich.steve_at_[hidden];
>
> I think this is the correct approach. And I agree with no PEM’s in header
> files.
>
>
>
> I am also in favor of getting this thing in place and letting it grow as
> necessary rather than letting it die in analysis paralysis.
>
>
>
> Steve
>
>
>
> *From:* Std-Proposals <std-proposals-bounces_at_[hidden]> *On Behalf
> Of *Rhidian De Wit via Std-Proposals
> *Sent:* Friday, June 19, 2026 3:01 PM
> *To:* std-proposals_at_[hidden]
> *Cc:* Rhidian De Wit <rhidiandewit_at_[hidden]>
> *Subject:* Re: [std-proposals] Translation-unit-local functions that
> access private class fields
>
>
>
> 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
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2026-06-19 22:23:28