Skip to content

Commit 1f29e91

Browse files
committed
Extend is() to correctly handle custom is operator
In current implementation custom is operator (`is_op()` member function) suffers from implicit casting of its argument. The same issue that were with function predicates. After this change we added the check if the cast is lossless or if the custom is operator is generic function. Added tests to verify is with various values. Added new concepts to make checks more readable: - valid_custom_is_operator - defined - to check if type is defined (used to determined generic fun) - predicate_member_fun - to check if member function is a predicate, Added new type trait: - argument_of_op_is_t to determined the type of op_is argument, Correct helper methods: - argument_of_helper - to handle ref-qualified member functions, - argument_of_helper - to work with non-copyable types,
1 parent 5d00337 commit 1f29e91

6 files changed

+191
-2
lines changed

include/cpp2util.h

+35-2
Original file line numberDiff line numberDiff line change
@@ -369,15 +369,33 @@ auto argument_of_helper(Ret(*) (Arg)) -> Arg;
369369
template<typename Ret, typename F, typename Arg>
370370
auto argument_of_helper(Ret(F::*) (Arg)) -> Arg;
371371

372+
template<typename Ret, typename F, typename Arg>
373+
auto argument_of_helper(Ret(F::*) (Arg)&) -> Arg;
374+
375+
template<typename Ret, typename F, typename Arg>
376+
auto argument_of_helper(Ret(F::*) (Arg)&&) -> Arg;
377+
372378
template<typename Ret, typename F, typename Arg>
373379
auto argument_of_helper(Ret(F::*) (Arg) const) -> Arg;
374380

381+
template<typename Ret, typename F, typename Arg>
382+
auto argument_of_helper(Ret(F::*) (Arg) const&) -> Arg;
383+
384+
template<typename Ret, typename F, typename Arg>
385+
auto argument_of_helper(Ret(F::*) (Arg) const&&) -> Arg;
386+
375387
template <typename F>
376-
auto argument_of_helper(F) -> CPP2_TYPEOF(argument_of_helper(&F::operator()));
388+
auto argument_of_helper(F const&) -> CPP2_TYPEOF(argument_of_helper(&F::operator()));
377389

378390
template <typename T>
379391
using argument_of_t = CPP2_TYPEOF(argument_of_helper(std::declval<T>()));
380392

393+
template <typename F>
394+
auto argument_of_helper_op_is(F const&) -> CPP2_TYPEOF(argument_of_helper(&F::op_is));
395+
396+
template <typename T>
397+
using argument_of_op_is_t = CPP2_TYPEOF(argument_of_helper_op_is(std::declval<T>()));
398+
381399
template <typename T>
382400
using pointee_t = std::iter_value_t<T>;
383401

@@ -428,6 +446,9 @@ concept brace_initializable_to = requires (From x) { To{x}; };
428446
template< typename X, typename C >
429447
concept same_type_as = std::same_as<std::remove_cvref_t<X>, std::remove_cvref_t<C>>;
430448

449+
template <typename X>
450+
concept defined = requires { std::declval<X>(); };
451+
431452
template <typename X>
432453
concept has_defined_argument = requires {
433454
std::declval<argument_of_t<X>>();
@@ -443,6 +464,18 @@ template <typename F, typename X>
443464
concept valid_predicate = (std::predicate<F, X> && !has_defined_argument<F>)
444465
|| (std::predicate<F, X> && has_defined_argument<F> && covertible_to_argument_of<X, F>);
445466

467+
template <typename X, typename O, auto mem_fun_ptr>
468+
concept predicate_member_fun = requires (X x, O o) {
469+
{ (o.*mem_fun_ptr)(x) } -> std::convertible_to<bool>;
470+
};
471+
472+
template <typename F, typename X>
473+
concept valid_custom_is_operator = predicate_member_fun<X, F, &F::op_is>
474+
&& (
475+
!defined<argument_of_op_is_t<F>>
476+
|| brace_initializable_to<X, argument_of_op_is_t<F>>
477+
);
478+
446479
//-----------------------------------------------------------------------
447480
//
448481
// General helpers
@@ -1299,7 +1332,7 @@ auto is( X const& x ) -> bool {
12991332
inline constexpr auto is( auto const& x, auto&& value ) -> bool
13001333
{
13011334
// Value with customized operator_is case
1302-
if constexpr (requires{ x.op_is(value); }) {
1335+
if constexpr (valid_custom_is_operator<decltype(x), decltype(value)>) {
13031336
return x.op_is(value);
13041337
}
13051338

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
fun: (name, v) = {
2+
std::cout << name << ": " <<
3+
inspect v -> std::string {
4+
is (42) = "42";
5+
is (123) = "op_is";
6+
is (-123) = "generic op_is";
7+
is (4321) = "comperable";
8+
is ("text") = "text";
9+
is _ = "unknown";
10+
}
11+
<< std::endl;
12+
}
13+
14+
main: () -> int = {
15+
fun("3.14", 3.14);
16+
fun("42", 42);
17+
fun("WithOp()", WithOp());
18+
fun("WithGenOp()", WithGenOp());
19+
fun("Cmp()", Cmp());
20+
fun("std::string(\"text\")", std::string("text"));
21+
fun("\"text\"", "text");
22+
fun("std::string_view(\"text\")", std::string_view("text"));
23+
fun(":std::vector = ('t','e','x','t','\\0')", :std::vector = ('t','e','x','t','\0'));
24+
}
25+
26+
WithOp : type = {
27+
op_is: (this, x : int) x == 123;
28+
}
29+
30+
WithGenOp : type = {
31+
op_is: (this, x) -> bool = {
32+
if constexpr std::convertible_to<decltype(x), int> {
33+
return x == -123;
34+
}
35+
return false;
36+
}
37+
}
38+
39+
Cmp : type = {
40+
operator==: (this, x : int) -> bool = x == 4321;
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
3.14: unknown
2+
42: 42
3+
WithOp(): unknown
4+
WithGenOp(): unknown
5+
Cmp(): comperable
6+
std::string("text"): text
7+
"text": text
8+
std::string_view("text"): text
9+
:std::vector = ('t','e','x','t','\0'): unknown

regression-tests/test-results/apple-clang-14/pure2-is-with-variable-and-value.cpp.output

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
2+
#define CPP2_IMPORT_STD Yes
3+
4+
//=== Cpp2 type declarations ====================================================
5+
6+
7+
#include "cpp2util.h"
8+
9+
#line 1 "pure2-is-with-variable-and-value.cpp2"
10+
11+
#line 26 "pure2-is-with-variable-and-value.cpp2"
12+
class WithOp;
13+
14+
15+
#line 30 "pure2-is-with-variable-and-value.cpp2"
16+
class WithGenOp;
17+
18+
19+
#line 39 "pure2-is-with-variable-and-value.cpp2"
20+
class Cmp;
21+
22+
23+
//=== Cpp2 type definitions and function declarations ===========================
24+
25+
#line 1 "pure2-is-with-variable-and-value.cpp2"
26+
auto fun(auto const& name, auto const& v) -> void;
27+
28+
#line 14 "pure2-is-with-variable-and-value.cpp2"
29+
[[nodiscard]] auto main() -> int;
30+
31+
#line 26 "pure2-is-with-variable-and-value.cpp2"
32+
class WithOp {
33+
public: [[nodiscard]] auto op_is(cpp2::impl::in<int> x) const& -> auto;
34+
public: WithOp() = default;
35+
public: WithOp(WithOp const&) = delete; /* No 'that' constructor, suppress copy */
36+
public: auto operator=(WithOp const&) -> void = delete;
37+
38+
#line 28 "pure2-is-with-variable-and-value.cpp2"
39+
};
40+
41+
class WithGenOp {
42+
public: [[nodiscard]] auto op_is(auto const& x) const& -> bool;
43+
public: WithGenOp() = default;
44+
public: WithGenOp(WithGenOp const&) = delete; /* No 'that' constructor, suppress copy */
45+
public: auto operator=(WithGenOp const&) -> void = delete;
46+
47+
48+
#line 37 "pure2-is-with-variable-and-value.cpp2"
49+
};
50+
51+
class Cmp {
52+
public: [[nodiscard]] auto operator==(cpp2::impl::in<int> x) const& -> bool;
53+
public: Cmp() = default;
54+
public: Cmp(Cmp const&) = delete; /* No 'that' constructor, suppress copy */
55+
public: auto operator=(Cmp const&) -> void = delete;
56+
57+
#line 41 "pure2-is-with-variable-and-value.cpp2"
58+
};
59+
60+
61+
//=== Cpp2 function definitions =================================================
62+
63+
#line 1 "pure2-is-with-variable-and-value.cpp2"
64+
auto fun(auto const& name, auto const& v) -> void{
65+
#line 2 "pure2-is-with-variable-and-value.cpp2"
66+
std::cout << name << ": " <<
67+
[&] () -> std::string { auto&& _expr = v;
68+
if (cpp2::impl::is(_expr, (42))) { if constexpr( requires{"42";} ) if constexpr( std::is_convertible_v<CPP2_TYPEOF(("42")),std::string> ) return "42"; else return std::string{}; else return std::string{}; }
69+
else if (cpp2::impl::is(_expr, 123)) { if constexpr( requires{"op_is";} ) if constexpr( std::is_convertible_v<CPP2_TYPEOF(("op_is")),std::string> ) return "op_is"; else return std::string{}; else return std::string{}; }
70+
else if (cpp2::impl::is(_expr, -123)) { if constexpr( requires{"generic op_is";} ) if constexpr( std::is_convertible_v<CPP2_TYPEOF(("generic op_is")),std::string> ) return "generic op_is"; else return std::string{}; else return std::string{}; }
71+
else if (cpp2::impl::is(_expr, 4321)) { if constexpr( requires{"comperable";} ) if constexpr( std::is_convertible_v<CPP2_TYPEOF(("comperable")),std::string> ) return "comperable"; else return std::string{}; else return std::string{}; }
72+
else if (cpp2::impl::is(_expr, "text")) { if constexpr( requires{"text";} ) if constexpr( std::is_convertible_v<CPP2_TYPEOF(("text")),std::string> ) return "text"; else return std::string{}; else return std::string{}; }
73+
else return "unknown"; }
74+
()
75+
<< std::endl;
76+
}
77+
78+
#line 14 "pure2-is-with-variable-and-value.cpp2"
79+
[[nodiscard]] auto main() -> int{
80+
fun("3.14", 3.14);
81+
fun("42", 42);
82+
fun("WithOp()", WithOp());
83+
fun("WithGenOp()", WithGenOp());
84+
fun("Cmp()", Cmp());
85+
fun("std::string(\"text\")", std::string("text"));
86+
fun("\"text\"", "text");
87+
fun("std::string_view(\"text\")", std::string_view("text"));
88+
fun(":std::vector = ('t','e','x','t','\\0')", std::vector{'t', 'e', 'x', 't', '\0'});
89+
}
90+
91+
#line 27 "pure2-is-with-variable-and-value.cpp2"
92+
[[nodiscard]] auto WithOp::op_is(cpp2::impl::in<int> x) const& -> auto { return x == 123; }
93+
94+
#line 31 "pure2-is-with-variable-and-value.cpp2"
95+
[[nodiscard]] auto WithGenOp::op_is(auto const& x) const& -> bool{
96+
if constexpr (std::convertible_to<decltype(x),int>) {
97+
return x == -123;
98+
}
99+
return false;
100+
}
101+
102+
#line 40 "pure2-is-with-variable-and-value.cpp2"
103+
[[nodiscard]] auto Cmp::operator==(cpp2::impl::in<int> x) const& -> bool { return x == 4321; }
104+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pure2-is-with-variable-and-value.cpp2... ok (all Cpp2, passes safety checks)
2+

0 commit comments

Comments
 (0)