hsutter / cppfront

A personal experimental C++ Syntax 2 -> Syntax 1 compiler
Other
5.39k stars 232 forks source link

[BUG] Can't constrain member function with UFCS on callable data member #885

Open JohelEGP opened 8 months ago

JohelEGP commented 8 months ago

Title: Can't constrain member function with UFCS on callable data member.

Description:

A few fundamental problems make it hard or impossible to constrain generic code that uses UFCS.

  1. Due to the order independence of function bodies, the use of the UFCS macro in the signature causes the closure type of the definition to mismatch that of the declaration. UFCS as a Cpp1 feature would solve this issue. [ Example: See https://cpp2.godbolt.org/z/ndhdTfchj. In this case, I think the onus can be on the author of the generic component. They can choose not use the UFCS syntax. -- end example ]
  2. The context of the function body

    • changes whether an expression actually performs UFCS (#550, #863, #864, #874), and
    • what names might be visible due to using statements (a problem in Cpp1, too).

    The same expression in a concept or requires can have a different meaning. For example, these calls can call a non-member in a concept, but not here:

    t: @struct type = {
     f: () = {
       a := :() = x.a(); // #550.
       b: std::function = :() = x.b(); // #863.
       c: std::optional = x.c(); // #864.
     }
     g: () x.g(); // #874.
    }

    In this case, the onus is on the author of the generic component.

Minimal reproducer (https://cpp2.godbolt.org/z/hz75Kaxhq):

```Cpp2 #define REQUIRES_EXPRESSION(...) requires { __VA_ARGS__ } #define COMPOUND_REQUIREMENT(E, C) { E } -> C; count_if: @struct type = { public pred: F; is_valid: < I: type, S: type >( in this, copy first: I, copy last: S ) -> move _ == { return std::bool_constant))>(); _ = first; _ = last; } operator(): < I: type, S: type >( in this, copy first: I, copy last: S ) -> move std::enable_if_t::value, std::iter_difference_t> requires (std::sentinel_for) /* (GCC) `error: no declaration matches`... * (Clang) After dropping `_NONLOCAL` from the UFCS macro (): * `error: out-of-line definition of 'operator()' does not match any declaration in 'count_if'`. * The closure type in the signature of the definition mismatches that of the declaration. * We need a way to constraint with the UFCS call `first*.pred()`. */ // && REQUIRES_EXPRESSION(COMPOUND_REQUIREMENT(first*.pred(), std::convertible_to)) = { count: = std::iter_difference_t(0); while first != last next first++ { if !first*.pred() { continue; } count++; } return count; } operator(): ( in this, forward r: R ) -> move std::ranges::range_difference_t = { return operator()(std::ranges::begin(r), std::ranges::end(r)); _ = r; } } main: () = { v := std::vector(3, 0); assert((:count_if = :(_) -> _ == true)(v) == 3); } ```

Commands: ```bash cppfront main.cpp2 clang++18 -std=c++23 -stdlib=libc++ -lc++abi -pedantic-errors -Wall -Wextra -Wconversion -Werror=unused-result -Werror=unused-value -Werror=unused-parameter -Werror=unused-variable -I . main.cpp ```

Cpp2 lowered to Cpp1: ```C++ //=== Cpp2 type declarations ==================================================== #include "cpp2util.h" #line 1 "/app/example.cpp2" #line 4 "/app/example.cpp2" template class count_if; //=== Cpp2 type definitions and function declarations =========================== #line 1 "/app/example.cpp2" #define REQUIRES_EXPRESSION(...) requires { __VA_ARGS__ } #define COMPOUND_REQUIREMENT(E, C) { E } -> C; #line 4 "/app/example.cpp2" template class count_if { public: F pred; public: template< typename I, typename S > [[nodiscard]] constexpr auto is_valid( I first, S last ) const& -> auto; #line 22 "/app/example.cpp2" public: template< typename I, typename S > [[nodiscard]] auto operator()( I first, S last ) const& -> std::enable_if_t::value,std::iter_difference_t> CPP2_REQUIRES_ ((std::sentinel_for)) #line 22 "/app/example.cpp2" ; #line 30 "/app/example.cpp2" /* (GCC) `error: no declaration matches`... (Clang) After dropping `_NONLOCAL` from the UFCS macro (): `error: out-of-line definition of 'operator()' does not match any declaration in 'count_if'`. The closure type in the signature of the definition mismatches that of the declaration. We need a way to constraint with the UFCS call `first.pred()`. */ // && REQUIRES_EXPRESSION(COMPOUND_REQUIREMENT(first*.pred(), std::convertible_to)) #line 51 "/app/example.cpp2" public: template [[nodiscard]] auto operator()( R&& r ) const& -> std::ranges::range_difference_t; #line 59 "/app/example.cpp2" }; auto main() -> int; //=== Cpp2 function definitions ================================================= #line 1 "/app/example.cpp2" #line 8 "/app/example.cpp2" template template< typename I, typename S > [[nodiscard]] constexpr auto count_if::is_valid( I first, S last ) const& -> auto { return std::bool_constant))>(); static_cast(std::move(first)); static_cast(std::move(last)); } template template< typename I, typename S > [[nodiscard]] auto count_if::operator()( I first, S last ) const& -> std::enable_if_t::value,std::iter_difference_t> requires ((std::sentinel_for)) #line 37 "/app/example.cpp2" { auto count {std::iter_difference_t(0)}; for( ; first != last; ++first ) { if (!(CPP2_UFCS(pred)((*cpp2::assert_not_null(first))))) { continue; } ++count; } return count; } template template [[nodiscard]] auto count_if::operator()( R&& r ) const& -> std::ranges::range_difference_t { return operator()(std::ranges::begin(r), std::ranges::end(r)); static_cast(CPP2_FORWARD(r)); } #line 61 "/app/example.cpp2" auto main() -> int{ auto v {std::vector(3, 0)}; cpp2::Default.expects((count_if{[]([[maybe_unused]] auto const& unnamed_param_1) -> auto { return true; }})(std::move(v)) == 3, ""); } ```