Did we not already consider https://github.com/WG21-SG14/SG14/blob/master/Docs/Proposals/NonAllocatingStandardFunction.pdf ? I'm not sure where that paper ended up or if it's still a proposal?

On Mon, Aug 17, 2026 at 8:19 AM Arthur O'Dwyer via SG14 <sg14@lists.isocpp.org> wrote:
On Mon, Aug 17, 2026 at 10:56 AM Kim J. Smith via SG14 <sg14@lists.isocpp.org> wrote:
> `F` satisfies the current `is_empty_trivial` test, but constructing an owning wrapper from `f` would normally construct a target object and therefore invoke the copy constructor. Eliminating the object also eliminates that observable effect.
> More generally, an empty object does not imply that its identity or lifetime is unobservable; `operator()` may also use `this`. So I suspect that trying to infer semantic statelessness from representation/type traits is difficult to make generally safe.

This is indeed an area where I didn't think things through thoroughly. I used to think `is_empty_trivial` does not indicate stateless only in non-owning semantics (`IsView` == true) and ignored the side effects in the lifetime management in owning semantics.

There are basically two categories of side effects:
1. side effects in lifetime management;
2. use of `this` in `operator()`.

For the first category, adding `std::is_trivially_move_constructible` and `std::is_trivially_copy_constructible` would be sufficient to eliminate the issue. However, there seems to be no reliable way to eliminate the second category.

You could "eliminate" it by relaxing the guarantees you document for your wrapper. Instead of documenting that you always call the stored callable, you could document that you call "the stored callable, or a copy of it," and furthermore document that you do not make copies unless the stored callable is trivially copyable (i.e. you guarantee never to make an expensive copy).
There is then no observable difference between

    EmptyTrivial t_;  // trivially default-constructible, trivially copyable, and empty
    void operator()() const { auto copy = t_; copy(); }  // what you document

and

    void operator()() const { EmptyTrivial copy; copy(); }  // what you actually do

In both cases, `&copy` is documented to be not-necessarily-the-same as `&t_`.

Vice versa, a type with a static operator() can't use `this` inside its `operator()`, but it could still have a non-trivial default constructor and/or copy and/or destructor.
I wouldn't try to detect static operator(); that seems like it could lead to SFINAE-unfriendly errors when the user wasn't expecting it.
    template<class T, class... Args>
    concept StaticCallable = requires { T::operator()(std::declval<Args>()...); };
You might also find pathological types such that `T::operator()(args...)` is well-formed but `t(args...)` calls a different overload.

(Yes, you can also find pathological types that are trivially copyable but `auto copy = t_;` calls a different overload! But that's a long-standing and "well-known" pathology: If someone gives you a type like that, I feel perfectly justified in saying "don't do that then." Playing games with static `operator()` is obviously newer and less well-studied.)

–Arthur
_______________________________________________
SG14 mailing list
SG14@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/sg14