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 23:01:26 +0200
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

Received on 2026-06-19 21:01:40