-
Notifications
You must be signed in to change notification settings - Fork 265
Description
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.
-
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 ] -
The context of the function body
- changes whether an expression actually performs UFCS ([BUG] Variable with placeholder type can't share name with member call in initializer #550, [BUG] Disable UFCS if the function needs to be captured #863, [BUG] Disable UFCS on function named after variable being initialized with CTAD #864, [BUG] Disable UFCS on deduced return type not yet deduced (
f: () x.f();) #874), and - what names might be visible due to
usingstatements (a problem in Cpp1, too).
The same expression in a
conceptorrequirescan have a different meaning.
For example, these calls can call a non-member in aconcept, 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.
- changes whether an expression actually performs UFCS ([BUG] Variable with placeholder type can't share name with member call in initializer #550, [BUG] Disable UFCS if the function needs to be captured #863, [BUG] Disable UFCS on function named after variable being initialized with CTAD #864, [BUG] Disable UFCS on deduced return type not yet deduced (
Minimal reproducer (https://cpp2.godbolt.org/z/hz75Kaxhq):
Details
#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.cppCpp2 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, "");
}