Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
JohelEGP opened this issue Dec 10, 2023 · 0 comments
Open
Labels
bug Something isn't working

Comments

@JohelEGP
Copy link
Contributor

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

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

#define REQUIRES_EXPRESSION(...) requires { __VA_ARGS__ }
#define COMPOUND_REQUIREMENT(E, C) { E } -> C;

count_if: @struct <F: type> type =
{
    public pred: F;

    is_valid: <
            I: type,
            S: type
        >(
        in this,
        copy first: I,
        copy last: S
    ) -> move _ ==
    {
        return std::bool_constant<REQUIRES_EXPRESSION(COMPOUND_REQUIREMENT(first*.pred(), std::convertible_to<bool>))>();
        _ = first;
        _ = last;
    }

    operator(): <
            I: type,
            S: type
        >(
        in this,
        copy first: I,
        copy last: S
    ) -> move std::enable_if_t<std::type_identity_t<decltype(is_valid(first, last))>::value, std::iter_difference_t<I>> requires (std::sentinel_for<S, I>)
        /* (GCC) `error: no declaration matches`...
         * (Clang) After dropping `_NONLOCAL` from the UFCS macro (<https://cpp2.godbolt.org/z/ThMfKKbE4>):
         * `error: out-of-line definition of 'operator()' does not match any declaration in 'count_if<F>'`.
         * 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<bool>))
      =
    {
        count: = std::iter_difference_t<I>(0);
        while first != last
            next first++
            {
                if !first*.pred()
                {
                    continue;
                }
                count++;
            }
        return count;
    }

    operator(): <R: type>(
        in this,
        forward r: R
    ) -> move std::ranges::range_difference_t<R> =
    {
        return operator()(std::ranges::begin(r), std::ranges::end(r));
        _ = r;
    }
}

main: () = {
  v := std::vector(3, 0);
  assert((:count_if = :(_) -> _ == true)(v) == 3);
}
Commands:
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:
//=== Cpp2 type declarations ====================================================


#include "cpp2util.h"

#line 1 "/app/example.cpp2"

#line 4 "/app/example.cpp2"
template<typename F> 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<typename F> 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<std::type_identity_t<decltype(is_valid(first, std::move(last)))>::value,std::iter_difference_t<I>>
CPP2_REQUIRES_ ((std::sentinel_for<S,I>))
#line 22 "/app/example.cpp2"
    ;

#line 30 "/app/example.cpp2"
        /* (GCC) `error: no declaration matches`...
          (Clang) After dropping `_NONLOCAL` from the UFCS macro (<https://cpp2.godbolt.org/z/ThMfKKbE4>):
          `error: out-of-line definition of 'operator()' does not match any declaration in 'count_if<F>'`.
          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<bool>))

#line 51 "/app/example.cpp2"
    public: template<typename R> [[nodiscard]] auto operator()(

        R&& r
    ) const& -> std::ranges::range_difference_t<R>;

#line 59 "/app/example.cpp2"
};

auto main() -> int;

//=== Cpp2 function definitions =================================================

#line 1 "/app/example.cpp2"

#line 8 "/app/example.cpp2"
    template <typename F> template<
            typename I,
            typename S
        > [[nodiscard]] constexpr auto count_if<F>::is_valid(

        I first,
        S last
    ) const& -> auto
    {
        return std::bool_constant<REQUIRES_EXPRESSION(COMPOUND_REQUIREMENT(CPP2_UFCS(pred)((*cpp2::assert_not_null(first))), std::convertible_to<bool>))>();
        static_cast<void>(std::move(first));
        static_cast<void>(std::move(last));
    }

    template <typename F> template<
            typename I,
            typename S
        > [[nodiscard]] auto count_if<F>::operator()(

        I first,
        S last
    ) const& -> std::enable_if_t<std::type_identity_t<decltype(is_valid(first, std::move(last)))>::value,std::iter_difference_t<I>>
requires ((std::sentinel_for<S,I>))

#line 37 "/app/example.cpp2"
    {
        auto count {std::iter_difference_t<I>(0)};
        for( ; first != last;
            ++first )
            {
                if (!(CPP2_UFCS(pred)((*cpp2::assert_not_null(first)))))
                {
                    continue;
                }
                ++count;
            }
        return count;
    }

    template <typename F> template<typename R> [[nodiscard]] auto count_if<F>::operator()(

        R&& r
    ) const& -> std::ranges::range_difference_t<R>
    {
        return operator()(std::ranges::begin(r), std::ranges::end(r));
        static_cast<void>(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, "");
}
@JohelEGP JohelEGP added the bug Something isn't working label Dec 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant