cppfront
cppfront copied to clipboard
[BUG] No direct UFCS syntax for (captured function object, dependent base member, function object prvalue)
Title: Can't capture function object for UFCS.
Description:
As an exercise, I tried implementing the CL combinators from Function Composition in Programming Languages - Conor Hoekstra - CppNorth 2023.
Implementations
At first, I tried using UFCS.
That doesn't work because the capture is the postfix-expression before the $.
That includes the object argument.
But I just wanted the function object.
Minimal reproducer (https://cpp2.godbolt.org/z/cYcb4c5YW):
w: (f) :(x) x.f$();
// w: (f) :(x) f$(x);
main: () = {
assert(w(:(x) -x;)(1) == -1);
}
Commands:
cppfront main.cpp2
clang++18 -std=c++23 -stdlib=libc++ -lc++abi -pedantic-errors -Wall -Wextra -Wconversion -Werror=unused-result -I . main.cpp
Expected result: Same as not using UFCS.
Actual result and error:
Cpp2 lowered to Cpp1:
//=== Cpp2 type declarations ====================================================
#include "cpp2util.h"
//=== Cpp2 type definitions and function declarations ===========================
[[nodiscard]] auto w(auto const& f) -> auto;
// w: (f) :(x) f$(x);
auto main() -> int;
//=== Cpp2 function definitions =================================================
[[nodiscard]] auto w(auto const& f) -> auto { return [_0 = x.f](auto const& x) -> auto { return _0(); }; }
auto main() -> int{
cpp2::Default.expects(w([](auto const& x) -> auto { return -x; })(1) == -1, "");
}
Output:
build/main.cpp:19:60: error: use of undeclared identifier 'x'
19 | [[nodiscard]] auto w(auto const& f) -> auto { return [_0 = x.f](auto const& x) -> auto { return _0(); }; }
| ^
build/main.cpp:19:97: error: use of undeclared identifier '_0'
19 | [[nodiscard]] auto w(auto const& f) -> auto { return [_0 = x.f](auto const& x) -> auto { return _0(); }; }
| ^
build/main.cpp:19:77: warning: unused parameter 'x' [-Wunused-parameter]
19 | [[nodiscard]] auto w(auto const& f) -> auto { return [_0 = x.f](auto const& x) -> auto { return _0(); }; }
| ^
build/main.cpp:19:34: warning: unused parameter 'f' [-Wunused-parameter]
19 | [[nodiscard]] auto w(auto const& f) -> auto { return [_0 = x.f](auto const& x) -> auto { return _0(); }; }
| ^
2 warnings and 2 errors generated.
From https://github.com/hsutter/cppfront/issues/741#issuecomment-1806938363:
. :(x) zip_transform(std::logical_or(), x, scan_left(x, true, std::not_equal_to())) ()
That's not valid grammar (https://cpp2.godbolt.org/z/9avjYh6sb):
Right, I like the
callhelper well enough that I'm waiting to see if there's really a need to write a new function expression in the middle of a postfix-expression... it's doable but is it needed?You could say it's needed for #748, so we could write
w: (f) :(x) x.(f$)();and havex.(f$)()call(f$)(x).
Also, can't do UFCS on a dependent base member (https://github.com/hsutter/cppfront/issues/741#issuecomment-1826408534):
. :(x) zip_transform(std::logical_or(), x, scan_left(x, true, std::not_equal_to())) ()
That's not valid grammar (https://cpp2.godbolt.org/z/9avjYh6sb):
Right, I like the
callhelper well enough that I'm waiting to see if there's really a need to write a new function expression in the middle of a postfix-expression... it's doable but is it needed?You could say it's needed for #748, so we could write
w: (f) :(x) x.(f$)();and havex.(f$)()call(f$)(x).There is another use case. In a member function of a type template, UFCS on a dependent base member requires explicit qualification (https://cpp2.godbolt.org/z/Mff1G9331):
u: @struct <T> type = { f: (_) true; } t: @struct <T> type = { this: u<T>; operator(): (this, r: std::span<const int>) -> _ = { for r do (e) { // OK. return this.f(e); // error: explicit qualification required to use member 'f' from dependent base class // 16 | return CPP2_UFCS_0(f, e); // | ^ return e.f(); // error: expected unqualified-id // 21 | return CPP2_UFCS_0(f, e.(*this)); // | ^ return e.this.f(); // OK. return e.u<T>::f(); // What I would like to write: // return e.(this.f)(); // That translates to this: return (this.f)(e); } } } main: () = { r: std::vector = (0, 1, 2); assert(t<int>()(r)); }
Also, can't do UFCS on a dependent base member (https://github.com/hsutter/cppfront/issues/741#issuecomment-1826408534):
For now, I can workaround this with using u<T>::f;, which makes return e.f(); well-formed: https://cpp2.godbolt.org/z/T5b5sxbYr.
Function object prvalue
I forgot to mention this other case where you first need to construct a callable.
Consider the call in the assert in the main above: t<int>()(r).
t<int>() is the function object we want to call.
Let's rewrite it in UFCS order: r.t<int>()().
The function to call becomes t<int> instead.
The second () applies to the result of the UFCS.
The call helper does help:
r.call(t<int>()) (https://cpp2.godbolt.org/z/Mfc5TP8xz).