On Sun, Sep 28, 2025 at 11:13 AM Bad At The Game via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
Hi, I tried to take another approach at UFCS, Here
https://github.com/ZXShady/proposals/blob/main/alternative_free_function_call_syntax.md
It isn't professionally written I tried my best.

FYI, if you were to elevate this into an actual WG21 proposal, it would need to have a real person's name on it, not "Shady".

FYI, your Clang patch seems to have a lot of extraneous diffs. I bet you ran `clang-format` on the entire codebase, or on the files you touched. Don't do that. Instead, run `git-clang-format --diff --extensions ',h,cpp' HEAD~4 --` to format only the exact diffs that you made, leaving the rest of the file's formatting intact.

Consider hyperlinking to CWG 1089 instead of the more cumbersome CWG 1089.

The idea seems identical to the pre-existing (and apparently stalled/abandoned) P2011 proposal of a "pipeline operator" — with two differences:
(1) You use the existing token `.` instead of P2011's new "pizza operator" `|>` (which required lexers to change to accept `|>` as a single token).
(2) You limit the right-hand-side "operand" to begin lexically with `identifier ::` in addition to whatever P2011 says.

Your discussion of CWG 1089 focuses on `p->T::f()`, not `p.T::f()`. Are you trying to propose that `p -> T::f()` should act the same as your new `(*p) . T::f()`, or differently from it? Either way seems bad. IMHO the original sin here was trying to reuse `.` as the pipeline operator, instead of picking a completely new operator like P2011's `|>`.

It's unclear to me (since you didn't provide wording) whether you intend to support both
    int x1 = 1;
    int y1 = x . std::max(2);
(where lookup finds an overload set of function templates) and also
    int x2 = 1;
    int y2 = x . std::ranges::max(2);
(where lookup finds a single global variable, which happens to be a niebloid). Ctrl+F for "ranges::", "nieb", "cpo" didn't find any discussion of what you mean by "only finds free functions."

Come to think of it, it's also unclear what you would do with:
    struct S { int f(int); static float f(S&, int); };
    S s;
    int y3 = s . S::f(2);
Is this now going to be rewritten into `S::f(s, 2)`, or will you keep the old behavior and make it call `s.f(2)`? Does anything change if the name `S` in this context refers to a namespace? Godbolt:
    namespace N {
      struct S {
        int f(int) { return 1; }
        static int f(S&, int) { return 2; }
      };
    }
    namespace S {
      int f(N::S&, int) { return 3; }
    }
    int main() {
      N::S s;
      return s.S::f(2);  // today this returns 1
    }
With P2011's pizza operator, the semantics are obvious (at least in this case):
      return s |> S::f(2);  // today this is illegal; tomorrow it rewrites into S::f(s, 2) and therefore returns 3, no ambiguity at all

Basically, you have some minor procedural problems with the proposal, and then you have a major fatal flaw due to your trying to reuse `.` (and maybe `->`?) as your pipeline operator instead of inventing a new one.

–Arthur