Skip to content

Commit e6a0200

Browse files
filipsajdakhsutter
andauthored
Extension of is() part1: free function predicates, fix implicit cast for build-in and custom is operator, refactoring (#1053)
* Extend is() to handle free function predicates This change address to issues: 1. implicit downcasting of arguments, 2. add support for predicates with generic type argument 1. Currently, if is() is checked against predicate the predicate argument can be implicitly casted that can lead to downcasting double to int equal_pi: (x:int) -> bool = { return x == 3; }; d := 3.14; if d is (equal_pi) { // d will be casted to int and will return true // ... } After this change downcasting will not happen no false positive/negative matches will happen. 2. Currently, if is() is checked against predicate with generic type it does not compile. After this change the code will compile and work as expected. * Add test for is with unnamed predicates Adding test for using is with lambdas, and generic lambdas. * Rewrite is() for variables and templates Adjust to new style of using one function for a match and series of constexpr ifs. There is a possibility to extend is() for matching std::integer_sequence but unfortunatelly clang is not support it (gcc & msvc do). template <template <typename, typename, typename...> class C, typename X> constexpr auto is( X const& ) { if constexpr (specialization_of_template<X, C>) { return std::true_type{}; } else { return std::false_type{}; } } template <template <typename, auto...> class C, typename X> constexpr auto is( X const& ) { if constexpr (specialization_of_template_type_and_nttp<X, C>) { return std::true_type{}; } else { return std::false_type{}; } } Alternatively we can support more matches for gcc & msvc by providing: #if defined(__clang__) template <template <typename...> class C, typename X> #else // allow us to support std::integer_sequence on gcc and msvc template <template <typename, typename, typename...> class C, typename X> #endif constexpr auto is( X const& ) { if constexpr (specialization_of_template<X, C>) { return std::true_type{}; } else { return std::false_type{}; } } #if defined(__clang__) template <template <typename, auto> class C, typename X> #else // allow us to support std::integer_sequence on gcc and msvc template <template <typename, auto...> class C, typename X> #endif constexpr auto is( X const& ) { if constexpr (specialization_of_template_type_and_nttp<X, C>) { return std::true_type{}; } else { return std::false_type{}; } } * 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, * Did two minor tweaks, and updated tests Add an array value-equality case (thanks to MSVC for warning about that) Also stop forwarding the `value` to `std::vformat` to stay compatible with the `vformat` change --------- Co-authored-by: Herb Sutter <[email protected]>
1 parent d5d3004 commit e6a0200

File tree

49 files changed

+628
-83
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+628
-83
lines changed

Diff for: include/cpp2util.h

+150-26
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,125 @@ using longdouble = long double;
356356
using _schar = signed char; // normally use i8 instead
357357
using _uchar = unsigned char; // normally use u8 instead
358358

359+
//-----------------------------------------------------------------------
360+
//
361+
// Helper for concepts
362+
//
363+
//-----------------------------------------------------------------------
364+
//
365+
366+
template<typename Ret, typename Arg>
367+
auto argument_of_helper(Ret(*) (Arg)) -> Arg;
368+
369+
template<typename Ret, typename F, typename Arg>
370+
auto argument_of_helper(Ret(F::*) (Arg)) -> Arg;
371+
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+
378+
template<typename Ret, typename F, typename Arg>
379+
auto argument_of_helper(Ret(F::*) (Arg) const) -> Arg;
380+
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+
387+
template <typename F>
388+
auto argument_of_helper(F const&) -> CPP2_TYPEOF(argument_of_helper(&F::operator()));
389+
390+
template <typename T>
391+
using argument_of_t = CPP2_TYPEOF(argument_of_helper(std::declval<T>()));
392+
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+
399+
template <typename T>
400+
using pointee_t = std::iter_value_t<T>;
401+
402+
template <template <typename...> class C, typename... Ts>
403+
constexpr auto specialization_of_template_helper(C< Ts...> const& ) -> std::true_type {
404+
return {};
405+
}
406+
407+
template <template <typename, auto...> class C, typename T, auto... Ns>
408+
requires (sizeof...(Ns) > 0)
409+
constexpr auto specialization_of_template_helper(C< T, Ns... > const& ) -> std::true_type {
410+
return {};
411+
}
412+
413+
//-----------------------------------------------------------------------
414+
//
415+
// Concepts
416+
//
417+
//-----------------------------------------------------------------------
418+
//
419+
420+
template <typename X, template<typename...> class C>
421+
concept specialization_of_template = requires (X x) {
422+
{ specialization_of_template_helper<C>(std::forward<X>(x)) } -> std::same_as<std::true_type>;
423+
};
424+
425+
template <typename X, template<typename,auto...> class C>
426+
concept specialization_of_template_type_and_nttp = requires (X x) {
427+
{ specialization_of_template_helper<C>(std::forward<X>(x)) } -> std::same_as<std::true_type>;
428+
};
429+
430+
template <typename X>
431+
concept dereferencable = requires (X x) { *x; };
432+
433+
template <typename X>
434+
concept default_constructible = std::is_default_constructible_v<std::remove_cvref_t<X>>;
435+
436+
template <typename X>
437+
concept bounded_array = std::is_bounded_array_v<std::remove_cvref_t<X>>;
438+
439+
template <typename X>
440+
concept pointer_like = dereferencable<X> && default_constructible<X> && std::equality_comparable<X>
441+
&& !bounded_array<X>;
442+
443+
template< typename From, typename To >
444+
concept brace_initializable_to = requires (From x) { To{x}; };
445+
446+
template< typename X, typename C >
447+
concept same_type_as = std::same_as<std::remove_cvref_t<X>, std::remove_cvref_t<C>>;
448+
449+
template <typename X>
450+
concept defined = requires { std::declval<X>(); };
451+
452+
template <typename X>
453+
concept has_defined_argument = requires {
454+
std::declval<argument_of_t<X>>();
455+
};
456+
457+
template <typename X, typename F>
458+
concept covertible_to_argument_of = same_type_as<X,argument_of_t<F>>
459+
|| (pointer_like<argument_of_t<F>> && brace_initializable_to<X, pointee_t<argument_of_t<F>>>)
460+
|| (!pointer_like<argument_of_t<F>> && brace_initializable_to<X, argument_of_t<F>>)
461+
;
462+
463+
template <typename F, typename X>
464+
concept valid_predicate = (std::predicate<F, X> && !has_defined_argument<F>)
465+
|| (std::predicate<F, X> && has_defined_argument<F> && covertible_to_argument_of<X, F>);
466+
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+
);
359478

360479
//-----------------------------------------------------------------------
361480
//
@@ -1149,31 +1268,24 @@ using empty = void;
11491268

11501269
// Templates
11511270
//
1152-
template <template <typename...> class C, typename... Ts>
1153-
constexpr auto is(C< Ts...> const& ) -> bool {
1154-
return true;
1155-
}
1156-
1157-
#if defined(_MSC_VER)
1158-
template <template <typename, typename...> class C, typename T>
1159-
constexpr auto is( T const& ) -> bool {
1160-
return false;
1271+
template <template <typename...> class C, typename X>
1272+
constexpr auto is( X&& ) {
1273+
if constexpr (specialization_of_template<X, C>) {
1274+
return std::true_type{};
11611275
}
1162-
#else
1163-
template <template <typename...> class C, typename T>
1164-
constexpr auto is( T const& ) -> bool {
1165-
return false;
1276+
else {
1277+
return std::false_type{};
11661278
}
1167-
#endif
1168-
1169-
template <template <typename,auto> class C, typename T, auto V>
1170-
constexpr auto is( C<T, V> const& ) -> bool {
1171-
return true;
11721279
}
11731280

1174-
template <template <typename,auto> class C, typename T>
1175-
constexpr auto is( T const& ) -> bool {
1176-
return false;
1281+
template <template <typename, auto> class C, typename X>
1282+
constexpr auto is( X&& ) {
1283+
if constexpr (specialization_of_template_type_and_nttp<X, C>) {
1284+
return std::true_type{};
1285+
}
1286+
else {
1287+
return std::false_type{};
1288+
}
11771289
}
11781290

11791291
// Types
@@ -1220,25 +1332,37 @@ auto is( X const& x ) -> bool {
12201332
inline constexpr auto is( auto const& x, auto&& value ) -> bool
12211333
{
12221334
// Value with customized operator_is case
1223-
if constexpr (requires{ x.op_is(value); }) {
1335+
if constexpr (valid_custom_is_operator<decltype(x), decltype(value)>) {
12241336
return x.op_is(value);
12251337
}
12261338

12271339
// Predicate case
1228-
else if constexpr (requires{ bool{ value(x) }; }) {
1340+
else if constexpr (valid_predicate<decltype(value), decltype(x)>) {
12291341
return value(x);
12301342
}
1231-
else if constexpr (std::is_function_v<decltype(value)> || requires{ &value.operator(); }) {
1343+
1344+
// Value equality case: C/C++ arrays or individual values
1345+
else if constexpr (std::is_array_v<CPP2_TYPEOF(x)> && std::is_array_v<CPP2_TYPEOF(value)>) {
1346+
if (std::ssize(x) == std::ssize(value)) {
1347+
return std::equal( std::begin(x), std::end(x), std::begin(value));
1348+
}
12321349
return false;
12331350
}
1234-
1235-
// Value equality case
12361351
else if constexpr (requires{ bool{x == value}; }) {
12371352
return x == value;
12381353
}
12391354
return false;
12401355
}
12411356

1357+
//-----------------------------------------------------------------------
1358+
//
1359+
// and "is predicate" for generic function used as predicate
1360+
//
1361+
1362+
template <typename X>
1363+
inline constexpr auto is( X const& x, bool (*value)(X const&) ) -> bool {
1364+
return value(x);
1365+
}
12421366

12431367
//-------------------------------------------------------------------------------------------------------------
12441368
// Built-in as
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
fun: (v) = {
2+
if v is (pred_i) {
3+
std::cout << "(v)$ is integer bigger than 3" << std::endl;
4+
}
5+
6+
if v is (pred_d) {
7+
std::cout << "(v)$ is double bigger than 3" << std::endl;
8+
}
9+
10+
if v is (pred_) {
11+
std::cout << "(v)$ is bigger than 3" << std::endl;
12+
}
13+
}
14+
15+
main: () -> int = {
16+
fun(3.14);
17+
fun(42);
18+
fun('a');
19+
}
20+
21+
pred_i: (x : int ) -> bool = {
22+
return x > 3;
23+
}
24+
25+
pred_d: (x : double ) -> bool = {
26+
return x > 3;
27+
}
28+
29+
pred_: (x) -> bool = {
30+
return x > 3;
31+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
fun: (v) = {
2+
if v is (:(x:int) x>3;) {
3+
std::cout << "(v)$ is integer bigger than 3" << std::endl;
4+
}
5+
6+
if v is (:(x:double) x>3;) {
7+
std::cout << "(v)$ is double bigger than 3" << std::endl;
8+
}
9+
10+
if v is (:(x) x>3;) {
11+
std::cout << "(v)$ is bigger than 3" << std::endl;
12+
}
13+
}
14+
15+
main: () -> int = {
16+
fun(3.14);
17+
fun(42);
18+
fun('a');
19+
}
+41
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) = "comparable";
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+
}

Diff for: regression-tests/test-results/apple-clang-14/mixed-inspect-values.cpp.execution

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ zero
99
3
1010
integer -42
1111
xyzzy
12-
3
12+
(no match)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
3.140000 is double bigger than 3
2+
3.140000 is bigger than 3
3+
42 is integer bigger than 3
4+
42 is bigger than 3
5+
a is integer bigger than 3
6+
a is bigger than 3

Diff for: regression-tests/test-results/apple-clang-14/pure2-is-with-free-functions-predicate.cpp.output

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
3.140000 is double bigger than 3
2+
3.140000 is bigger than 3
3+
42 is integer bigger than 3
4+
42 is bigger than 3
5+
a is integer bigger than 3
6+
a is bigger than 3

Diff for: regression-tests/test-results/apple-clang-14/pure2-is-with-unnamed-predicates.cpp.output

Whitespace-only changes.
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

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

Whitespace-only changes.

0 commit comments

Comments
 (0)