Date: Sat, 9 May 2026 23:43:50 +0200
I had time to think, and I have many thoughts.
This will be a long email. Sorry. You have been warned.
We seem to be getting more and more into implementation details.
Maybe we should move this conversation elsewhere to not spam the mailing
list?
Is this fair use or abuse?
I. Multiple Approaches
It is obvious that we all come from different backgrounds.
Upon reflection (pun intended) I realized what's different about my
approach.
I seem to favor solutions with "nice properties" and I preach about the
"identity" of a class, while You just want to define a damn private
function.
My approach is very much a "mathematician's approach" (I am one), while
Your approach is much more "practical".
Probably the best solution (if such a thing exists) lies somewhere in
between.
I just want to say, I represent one extreme, and I am aware of that.
We seem to have two main approaches, one with "reopening the class private
scope" and one without, and so far I am not convinced that any one is
better than the other.
I do have a preference, but only because I played around with it more than
the other approach.
I propose that we explore and describe both.
There may be more we haven't thought of yet.
We don't need to agree on any one unanimously.
We can present more than one in the first proposal.
We only need to be thorough and extensive.
II. Next Steps
I get the impression that it is *very* difficult to get anything new into
the C++ standard.
Surely a core language feature proposal will be submitted to a lot of
criticism from all angles.
Therefore we should try to anticipate as many arguments as we can.
So far I haven't seen any red flags that the core idea (hiding non-ABI
relevant private members) would clash with the core principles of C++, but
how can we be sure?
I also thought about creating a short questionnaire we could send out to
the C++ community to gauge reception of the various approaches.
I realized, I am not the "average C++ programmer".
I would love to see real data on what syntax/ruleset they think is
confusing or clear, stuff like that.
III. Thoughts on ODR violation and overload resolution
Current class member functions have a nice property; it doesn't matter
where we put the function body, because all member functions are declared
upfront, member function calls always resolve the same way, e.g.
// Header
class MyClass {
public:
void Bar() { Foo(3); } // calls Foo(int)
void Foo(double);
private:
void Foo(int);
};
// .cpp
void MyClass::Foo(int) { ... }
void MyClass::Foo(double) { ... }
is exactly the same as
// Header
class MyClass {
public:
void Bar();
void Foo(double);
private:
void Foo(int);
};
// .cpp
void MyClass::Bar() { Foo(3); }
void MyClass::Foo(int) { ... }
void MyClass::Foo(double) { ... }
Let's turn Foo(int) into a hidden private function:
// Header
class MyClass {
public:
void Bar() { Foo(3); } // calls Foo(double)
void Foo(double);
};
// .cpp
private void MyClass::Foo(int) { ... }
void MyClass::Foo(double) { ... }
It is clear that the inline Bar() must not invoke Foo(int) to avoid ODR
violation.
But now if we move the definition of Bar() out of the class:
// Header
class MyClass {
public:
void Bar();
void Foo(double);
};
// .cpp
private void MyClass::Foo(int) { ... }
void MyClass::Foo(double) { ... }
void MyClass::Bar() { Foo(3); } // calls Foo(int) now? It didn't before...
AND we allow Bar() to invoke Foo(int), then we break the "nice property".
It wouldn't be an ODR violation, but it opens the door to sneaky bugs and
unexpected function calls because we (C++ programmers) are already
accustomed to the way classes work today.
Is this inherently bad? Well no, free functions already work like this:
void Foo(int);
void Bar() { foo(3.2); } // Calls Foo(int)
void Foo(double);
// Versus
void Foo(int);
void Foo(double);
void Bar() { foo(3.2); } // Calls Foo(double)
but I feel like this would be a step backwards in terms of language safety
and bug prevention.
It would also increase mental load.
Instead I propose that hidden private functions are not visible to name
lookup/overload resolution by default, e.g:
// Header
class MyClass {
public:
void Bar();
void Foo(double);
};
// .cpp
private void MyClass::Foo(int) { ... }
void MyClass::Foo(double) { ... }
void MyClass::Bar() { Foo(3); } // calls Foo(double)
Bar() should call Foo(double) because it would call Foo(double) if it was
inlined.
I propose that hidden private functions must be invoked with a special
syntax, e.g.
void MyClass::Bar() { private Foo(3); } // Looks a bit weird though
// OR
void MyClass::Bar() { this->private Foo(3); } // A little better
This is very similar to template disambiguation syntax:
auto l = []<class>(){};
l.template operator()<int>();
For convenience, we could introduce a new using statement:
// .cpp
private void MyClass::Foo(int) { ... }
void MyClass::Foo(double) { ... }
void MyClass::Bar() {
using private; // Introduce all hidden private entities for name
lookup/overload resolution
Foo(3);
}
This can work with constructors as well:
private MyClass::MyClass(int) { ... }
MyClass::MyClass() : private MyClass(42) { ... }
Also, I feel like we should allow hidden private default constructors too.
There's nothing special about them.
If there's already an implicitly defined one, that's a compiler error.
While we are here, IMO this syntax would be nicer when put next to other
member functions:
void MyClass::private Foo(int);
void MyClass::Bar() { ... }
void MyClass::private Foo(int) { ... }
IV. Should hidden private functions have internal or external linkage by
default?
Given the option, I feel like internal linkage would be chosen in 99% of
the cases, but we should not jump to conclusions just yet.
Sometimes the implementation of a class is broken up into multiple .cpp
files, and we should support this use case too.
What do you think about this:
namespace {
void MyClass::private Foo(int);
}
The rule could be that the function declaration/definition must happen
either in the same namespace as the class or in an unnamed namespace within
that. E.g.
// Header
namespace N { class MyClass{...}; }
// .cpp
namespace N {
void MyClass::private Foo(int); // External linkage
namespace {
void MyClass::private Baz(); // Internal linkage
}
}
If we go with this approach and not "reopening the class scope", then we
can choose linkage for each function independently.
If we want to have similar flexibility, then we must allow "multiple
reopenings" of the class scope, but I don't endorse that.
Sincerely,
Matthew
On Thu, 30 Apr 2026 at 18:06, André Offringa via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> On 4/30/26 01:25, Rhidian De Wit via Std-Proposals wrote:
>
> [..]
> Maybe PEM's should not be allowed to be overloaded with non-static member
> functions of the class? As in, the *private void Foo::F(int) {}* declaration
> would give a compiler error as it overloads a non-static member function of
> *Foo*. This would avoid confusion with programmers (and make teaching
> this more simple) as otherwise there'd be the need to explain why *F(5)* is
> resolving to *F(double)* rather than *F(int)*, which is the better
> fitting match.
>
>
> Thanks for all the input. I have some new questions. Not allowing
> overloading with private extension member functions could indeed be a way
> to prevent the addition of new ways of violating ODR with this proposal. On
> the other hand, it would make the feature slightly less strong, overall.
>
> I see four possible options:
>
> 1) Private extension member functions (PEMFs) may not overload at all.
> It is a simple rule, and a relatively consistent rule but constrains the
> feature the most. I believe this solves all newly created ODR issues.
>
> 2) PEMFs may only overload other PEMFs.
> This is slightly less simple and slightly less consistent (could produce
> "why is this allowed and that not?!" reactions), but also slightly less
> constraining, and allows reusing overloaded functions and extending them
> (see below). This solves most ODR issues, and I think only leaves very rare
> contrived ways of breaking ODR not much different from what functions can
> already do (e.g. for a template class, declare one PEMF overload in the
> header file, one in the implementation file and cause it to be instantiated
> after the header file + after the 2nd PEMF).
>
> 3) As option 2), but also allow overloading constructors.
> Constructors are special, and PEMF constructors might be useful despite
> restrictions (example below). PEMF constructors can still break ODR.
>
> 4) Allow (all) overloading.
> The result is that PEMFs can introduce new ways of violating ODR, but
> allowing this allows for slightly more flexibility in the class design.
>
> I think the 2nd option is a good compromise. I feel that if we allow
> overloading, the way that PEMFs can break ODR by introducing overloads is
> not thát trivial to avoid, i.e. one might break ODR without realizing it.
> If we go for option 4, I could imagine that a "best practice" becomes
> "never overload member functions with PEMFs", so if so, why not enforce it?
> The loss in functionality of overloading seems relatively small, but is not
> negligible either.
>
> Both options 1) and 2) mean that a (delegated) constructors can never be a
> PEMFs. I think that it's somewhat rare that a delegated constructor would
> add dependencies, so the motivation for a PEMF constructor is I think
> small. That said, it's not unthinkable that one wants to initialize the
> class data members using a delegated constructor, and that for that the
> constructor needs to have a signature that has dependencies. Without PEMF
> constructors, these dependencies are then part of the class declaration. I
> think that PEMF constructors are thus more desirable than regular function
> overloading.
>
> With option 2, the functionality of regular function overloading could
> still be created by declaring PEMF overloads with a new name that fwds to
> the non-PEMF for types that it doesn't extend. This way, an overloaded
> function with extension can be made.
>
> A PEMF constructor can break ODR just as a regular function, so I think
> option 3 makes less sense, and is I think the worst balance between
> expressiveness and limiting surprises.
>
> When we taking a safe approach (1 or 2) which is found to be too
> constraining after all, it could be changed to a later option in the list,
> without backward breaking changes.
>
> My questions:
> - Do people agree option 2 is most sensible, or am I overlooking something?
> - Would disallowing overloads (option 1) indeed stop all new ways of
> breaking the ODR allowed by this proposal, and does option 2 indeed stop
> all but contrived ODR breaks?
>
> Regards,
> André
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
This will be a long email. Sorry. You have been warned.
We seem to be getting more and more into implementation details.
Maybe we should move this conversation elsewhere to not spam the mailing
list?
Is this fair use or abuse?
I. Multiple Approaches
It is obvious that we all come from different backgrounds.
Upon reflection (pun intended) I realized what's different about my
approach.
I seem to favor solutions with "nice properties" and I preach about the
"identity" of a class, while You just want to define a damn private
function.
My approach is very much a "mathematician's approach" (I am one), while
Your approach is much more "practical".
Probably the best solution (if such a thing exists) lies somewhere in
between.
I just want to say, I represent one extreme, and I am aware of that.
We seem to have two main approaches, one with "reopening the class private
scope" and one without, and so far I am not convinced that any one is
better than the other.
I do have a preference, but only because I played around with it more than
the other approach.
I propose that we explore and describe both.
There may be more we haven't thought of yet.
We don't need to agree on any one unanimously.
We can present more than one in the first proposal.
We only need to be thorough and extensive.
II. Next Steps
I get the impression that it is *very* difficult to get anything new into
the C++ standard.
Surely a core language feature proposal will be submitted to a lot of
criticism from all angles.
Therefore we should try to anticipate as many arguments as we can.
So far I haven't seen any red flags that the core idea (hiding non-ABI
relevant private members) would clash with the core principles of C++, but
how can we be sure?
I also thought about creating a short questionnaire we could send out to
the C++ community to gauge reception of the various approaches.
I realized, I am not the "average C++ programmer".
I would love to see real data on what syntax/ruleset they think is
confusing or clear, stuff like that.
III. Thoughts on ODR violation and overload resolution
Current class member functions have a nice property; it doesn't matter
where we put the function body, because all member functions are declared
upfront, member function calls always resolve the same way, e.g.
// Header
class MyClass {
public:
void Bar() { Foo(3); } // calls Foo(int)
void Foo(double);
private:
void Foo(int);
};
// .cpp
void MyClass::Foo(int) { ... }
void MyClass::Foo(double) { ... }
is exactly the same as
// Header
class MyClass {
public:
void Bar();
void Foo(double);
private:
void Foo(int);
};
// .cpp
void MyClass::Bar() { Foo(3); }
void MyClass::Foo(int) { ... }
void MyClass::Foo(double) { ... }
Let's turn Foo(int) into a hidden private function:
// Header
class MyClass {
public:
void Bar() { Foo(3); } // calls Foo(double)
void Foo(double);
};
// .cpp
private void MyClass::Foo(int) { ... }
void MyClass::Foo(double) { ... }
It is clear that the inline Bar() must not invoke Foo(int) to avoid ODR
violation.
But now if we move the definition of Bar() out of the class:
// Header
class MyClass {
public:
void Bar();
void Foo(double);
};
// .cpp
private void MyClass::Foo(int) { ... }
void MyClass::Foo(double) { ... }
void MyClass::Bar() { Foo(3); } // calls Foo(int) now? It didn't before...
AND we allow Bar() to invoke Foo(int), then we break the "nice property".
It wouldn't be an ODR violation, but it opens the door to sneaky bugs and
unexpected function calls because we (C++ programmers) are already
accustomed to the way classes work today.
Is this inherently bad? Well no, free functions already work like this:
void Foo(int);
void Bar() { foo(3.2); } // Calls Foo(int)
void Foo(double);
// Versus
void Foo(int);
void Foo(double);
void Bar() { foo(3.2); } // Calls Foo(double)
but I feel like this would be a step backwards in terms of language safety
and bug prevention.
It would also increase mental load.
Instead I propose that hidden private functions are not visible to name
lookup/overload resolution by default, e.g:
// Header
class MyClass {
public:
void Bar();
void Foo(double);
};
// .cpp
private void MyClass::Foo(int) { ... }
void MyClass::Foo(double) { ... }
void MyClass::Bar() { Foo(3); } // calls Foo(double)
Bar() should call Foo(double) because it would call Foo(double) if it was
inlined.
I propose that hidden private functions must be invoked with a special
syntax, e.g.
void MyClass::Bar() { private Foo(3); } // Looks a bit weird though
// OR
void MyClass::Bar() { this->private Foo(3); } // A little better
This is very similar to template disambiguation syntax:
auto l = []<class>(){};
l.template operator()<int>();
For convenience, we could introduce a new using statement:
// .cpp
private void MyClass::Foo(int) { ... }
void MyClass::Foo(double) { ... }
void MyClass::Bar() {
using private; // Introduce all hidden private entities for name
lookup/overload resolution
Foo(3);
}
This can work with constructors as well:
private MyClass::MyClass(int) { ... }
MyClass::MyClass() : private MyClass(42) { ... }
Also, I feel like we should allow hidden private default constructors too.
There's nothing special about them.
If there's already an implicitly defined one, that's a compiler error.
While we are here, IMO this syntax would be nicer when put next to other
member functions:
void MyClass::private Foo(int);
void MyClass::Bar() { ... }
void MyClass::private Foo(int) { ... }
IV. Should hidden private functions have internal or external linkage by
default?
Given the option, I feel like internal linkage would be chosen in 99% of
the cases, but we should not jump to conclusions just yet.
Sometimes the implementation of a class is broken up into multiple .cpp
files, and we should support this use case too.
What do you think about this:
namespace {
void MyClass::private Foo(int);
}
The rule could be that the function declaration/definition must happen
either in the same namespace as the class or in an unnamed namespace within
that. E.g.
// Header
namespace N { class MyClass{...}; }
// .cpp
namespace N {
void MyClass::private Foo(int); // External linkage
namespace {
void MyClass::private Baz(); // Internal linkage
}
}
If we go with this approach and not "reopening the class scope", then we
can choose linkage for each function independently.
If we want to have similar flexibility, then we must allow "multiple
reopenings" of the class scope, but I don't endorse that.
Sincerely,
Matthew
On Thu, 30 Apr 2026 at 18:06, André Offringa via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> On 4/30/26 01:25, Rhidian De Wit via Std-Proposals wrote:
>
> [..]
> Maybe PEM's should not be allowed to be overloaded with non-static member
> functions of the class? As in, the *private void Foo::F(int) {}* declaration
> would give a compiler error as it overloads a non-static member function of
> *Foo*. This would avoid confusion with programmers (and make teaching
> this more simple) as otherwise there'd be the need to explain why *F(5)* is
> resolving to *F(double)* rather than *F(int)*, which is the better
> fitting match.
>
>
> Thanks for all the input. I have some new questions. Not allowing
> overloading with private extension member functions could indeed be a way
> to prevent the addition of new ways of violating ODR with this proposal. On
> the other hand, it would make the feature slightly less strong, overall.
>
> I see four possible options:
>
> 1) Private extension member functions (PEMFs) may not overload at all.
> It is a simple rule, and a relatively consistent rule but constrains the
> feature the most. I believe this solves all newly created ODR issues.
>
> 2) PEMFs may only overload other PEMFs.
> This is slightly less simple and slightly less consistent (could produce
> "why is this allowed and that not?!" reactions), but also slightly less
> constraining, and allows reusing overloaded functions and extending them
> (see below). This solves most ODR issues, and I think only leaves very rare
> contrived ways of breaking ODR not much different from what functions can
> already do (e.g. for a template class, declare one PEMF overload in the
> header file, one in the implementation file and cause it to be instantiated
> after the header file + after the 2nd PEMF).
>
> 3) As option 2), but also allow overloading constructors.
> Constructors are special, and PEMF constructors might be useful despite
> restrictions (example below). PEMF constructors can still break ODR.
>
> 4) Allow (all) overloading.
> The result is that PEMFs can introduce new ways of violating ODR, but
> allowing this allows for slightly more flexibility in the class design.
>
> I think the 2nd option is a good compromise. I feel that if we allow
> overloading, the way that PEMFs can break ODR by introducing overloads is
> not thát trivial to avoid, i.e. one might break ODR without realizing it.
> If we go for option 4, I could imagine that a "best practice" becomes
> "never overload member functions with PEMFs", so if so, why not enforce it?
> The loss in functionality of overloading seems relatively small, but is not
> negligible either.
>
> Both options 1) and 2) mean that a (delegated) constructors can never be a
> PEMFs. I think that it's somewhat rare that a delegated constructor would
> add dependencies, so the motivation for a PEMF constructor is I think
> small. That said, it's not unthinkable that one wants to initialize the
> class data members using a delegated constructor, and that for that the
> constructor needs to have a signature that has dependencies. Without PEMF
> constructors, these dependencies are then part of the class declaration. I
> think that PEMF constructors are thus more desirable than regular function
> overloading.
>
> With option 2, the functionality of regular function overloading could
> still be created by declaring PEMF overloads with a new name that fwds to
> the non-PEMF for types that it doesn't extend. This way, an overloaded
> function with extension can be made.
>
> A PEMF constructor can break ODR just as a regular function, so I think
> option 3 makes less sense, and is I think the worst balance between
> expressiveness and limiting surprises.
>
> When we taking a safe approach (1 or 2) which is found to be too
> constraining after all, it could be changed to a later option in the list,
> without backward breaking changes.
>
> My questions:
> - Do people agree option 2 is most sensible, or am I overlooking something?
> - Would disallowing overloads (option 1) indeed stop all new ways of
> breaking the ODR allowed by this proposal, and does option 2 indeed stop
> all but contrived ODR breaks?
>
> Regards,
> André
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
-- Üdv.: Máté
Received on 2026-05-09 21:44:04
