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