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@lists.isocpp.org>:

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@lists.isocpp.org>
Gesendet: Fr 19.06.2026 20:44
Betreff: Re: [std-proposals] Translation-unit-local functions that access private class fields
An: std-proposals@lists.isocpp.org;
CC: Rhidian De Wit <rhidiandewit@gmail.com>;
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@lists.isocpp.org>:
On Thu, 28 May 2026, 16:51 Máté Ték via Std-Proposals, <std-proposals@lists.isocpp.org> 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@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
 

--
Rhidian De Wit
Software Engineer - Barco
-- 
 Std-Proposals mailing list
 Std-Proposals@lists.isocpp.org
 https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
 
--
Std-Proposals mailing list
Std-Proposals@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals


--
Rhidian De Wit
Software Engineer - Barco