cppfront icon indicating copy to clipboard operation
cppfront copied to clipboard

[BUG] No direct UFCS syntax for (captured function object, dependent base member, function object prvalue)

Open JohelEGP opened this issue 2 years ago • 4 comments
trafficstars

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

https://cpp2.godbolt.org/z/WjTTnf383: 1699742209 1697146848 1697146876

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.

JohelEGP avatar Oct 12 '23 21:10 JohelEGP

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 call helper 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 have x.(f$)() call (f$)(x).

JohelEGP avatar Nov 11 '23 23:11 JohelEGP

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 call helper 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 have x.(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));
}

JohelEGP avatar Nov 25 '23 20:11 JohelEGP

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.

JohelEGP avatar Nov 25 '23 20:11 JohelEGP

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).

JohelEGP avatar Nov 26 '23 01:11 JohelEGP