From: Jason Merrill Date: Tue, 5 Nov 2019 23:56:18 +0000 (-0500) Subject: Implement C++20 operator<=>. X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=b7689b962dd6536bbb2567bfdec52e35109af7ab;p=gcc.git Implement C++20 operator<=>. There are three major pieces to this support: scalar operator<=>, synthesis of comparison operators, and rewritten/reversed overload resolution (e.g. a < b becomes 0 > b <=> a). Unlike other defaulted functions, where we use synthesized_method_walk to semi-simulate what the definition of the function will be like, this patch determines the characteristics of a comparison operator by trying to define it. My handling of non-dependent rewritten operators in templates can still use some work: build_min_non_dep_op_overload can't understand the rewrites and crashes, so I'm avoiding it for now by clearing *overload. This means we'll do name lookup again at instantiation time, which can incorrectly mean a different result. I'll poke at this more in stage 3. I'm leaving out a fourth section ("strong structural equality") even though I've implemented it, because it seems likely to change radically tomorrow. Thanks to Tim van Deurzen and Jakub for implementing lexing of the <=> operator, and Jonathan for the initial header. gcc/cp/ * cp-tree.h (struct lang_decl_fn): Add maybe_deleted bitfield. (DECL_MAYBE_DELETED): New. (enum special_function_kind): Add sfk_comparison. (LOOKUP_REWRITTEN, LOOKUP_REVERSED): New. * call.c (struct z_candidate): Add rewritten and reversed methods. (add_builtin_candidate): Handle SPACESHIP_EXPR. (add_builtin_candidates): Likewise. (add_candidates): Don't add a reversed candidate if the parms are the same. (add_operator_candidates): Split out from build_new_op_1. Handle rewritten and reversed candidates. (add_candidate): Swap conversions of reversed candidate. (build_new_op_1): Swap them back. Build a second operation for rewritten candidates. (extract_call_expr): Handle rewritten calls. (same_fn_or_template): New. (joust): Handle rewritten and reversed candidates. * class.c (add_implicitly_declared_members): Add implicit op==. (classtype_has_op, classtype_has_defaulted_op): New. * constexpr.c (cxx_eval_binary_expression): Handle SPACESHIP_EXPR. (cxx_eval_constant_expression, potential_constant_expression_1): Likewise. * cp-gimplify.c (genericize_spaceship): New. (cp_genericize_r): Use it. * cp-objcp-common.c (cp_common_init_ts): Handle SPACESHIP_EXPR. * decl.c (finish_function): Handle deleted function. * decl2.c (grokfield): SET_DECL_FRIEND_CONTEXT on defaulted friend. (mark_used): Check DECL_MAYBE_DELETED. Remove assumption that defaulted functions are non-static members. * error.c (dump_expr): Handle SPACESHIP_EXPR. * method.c (type_has_trivial_fn): False for sfk_comparison. (enum comp_cat_tag, struct comp_cat_info_t): New types. (comp_cat_cache): New array variable. (lookup_comparison_result, lookup_comparison_category) (is_cat, cat_tag_for, spaceship_comp_cat) (spaceship_type, genericize_spaceship) (common_comparison_type, early_check_defaulted_comparison) (comp_info, build_comparison_op): New. (synthesize_method): Handle sfk_comparison. Handle deleted. (get_defaulted_eh_spec, maybe_explain_implicit_delete) (explain_implicit_non_constexpr, implicitly_declare_fn) (defaulted_late_check, defaultable_fn_check): Handle sfk_comparison. * name-lookup.c (get_std_name_hint): Add comparison categories. * tree.c (special_function_p): Add sfk_comparison. * typeck.c (cp_build_binary_op): Handle SPACESHIP_EXPR. 2019-11-05 Tim van Deurzen Add new tree code for the spaceship operator. gcc/cp/ * cp-tree.def: Add new tree code. * operators.def: New binary operator. * parser.c: Add new token and tree code. libcpp/ * cpplib.h: Add spaceship operator for C++. * lex.c: Implement conditional lexing of spaceship operator for C++20. 2019-11-05 Jonathan Wakely libstdc++-v3/ * libsupc++/compare: New header. * libsupc++/Makefile.am (std_HEADERS): Add compare. * include/std/version: Define __cpp_lib_three_way_comparison. * include/std/functional: #include . From-SVN: r277865 --- diff --git a/gcc/c-family/c-cppbuiltin.c b/gcc/c-family/c-cppbuiltin.c index f9cd76b2c86..cf3d437fc37 100644 --- a/gcc/c-family/c-cppbuiltin.c +++ b/gcc/c-family/c-cppbuiltin.c @@ -990,6 +990,7 @@ c_cpp_builtins (cpp_reader *pfile) cpp_define (pfile, "__cpp_nontype_template_parameter_class=201806L"); cpp_define (pfile, "__cpp_impl_destroying_delete=201806L"); cpp_define (pfile, "__cpp_constexpr_dynamic_alloc=201907L"); + cpp_define (pfile, "__cpp_impl_three_way_comparison=201907L"); } if (flag_concepts) { diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog index 560896a1dd8..d0b47f7c562 100644 --- a/gcc/cp/ChangeLog +++ b/gcc/cp/ChangeLog @@ -1,3 +1,59 @@ +2019-11-05 Jason Merrill + + Implement C++20 operator<=>. + * cp-tree.h (struct lang_decl_fn): Add maybe_deleted bitfield. + (DECL_MAYBE_DELETED): New. + (enum special_function_kind): Add sfk_comparison. + (LOOKUP_REWRITTEN, LOOKUP_REVERSED): New. + * call.c (struct z_candidate): Add rewritten and reversed methods. + (add_builtin_candidate): Handle SPACESHIP_EXPR. + (add_builtin_candidates): Likewise. + (add_candidates): Don't add a reversed candidate if the parms are + the same. + (add_operator_candidates): Split out from build_new_op_1. Handle + rewritten and reversed candidates. + (add_candidate): Swap conversions of reversed candidate. + (build_new_op_1): Swap them back. Build a second operation for + rewritten candidates. + (extract_call_expr): Handle rewritten calls. + (same_fn_or_template): New. + (joust): Handle rewritten and reversed candidates. + * class.c (add_implicitly_declared_members): Add implicit op==. + (classtype_has_op, classtype_has_defaulted_op): New. + * constexpr.c (cxx_eval_binary_expression): Handle SPACESHIP_EXPR. + (cxx_eval_constant_expression, potential_constant_expression_1): + Likewise. + * cp-gimplify.c (genericize_spaceship): New. + (cp_genericize_r): Use it. + * cp-objcp-common.c (cp_common_init_ts): Handle SPACESHIP_EXPR. + * decl.c (finish_function): Handle deleted function. + * decl2.c (grokfield): SET_DECL_FRIEND_CONTEXT on defaulted friend. + (mark_used): Check DECL_MAYBE_DELETED. Remove assumption that + defaulted functions are non-static members. + * error.c (dump_expr): Handle SPACESHIP_EXPR. + * method.c (type_has_trivial_fn): False for sfk_comparison. + (enum comp_cat_tag, struct comp_cat_info_t): New types. + (comp_cat_cache): New array variable. + (lookup_comparison_result, lookup_comparison_category) + (is_cat, cat_tag_for, spaceship_comp_cat) + (spaceship_type, genericize_spaceship) + (common_comparison_type, early_check_defaulted_comparison) + (comp_info, build_comparison_op): New. + (synthesize_method): Handle sfk_comparison. Handle deleted. + (get_defaulted_eh_spec, maybe_explain_implicit_delete) + (explain_implicit_non_constexpr, implicitly_declare_fn) + (defaulted_late_check, defaultable_fn_check): Handle sfk_comparison. + * name-lookup.c (get_std_name_hint): Add comparison categories. + * tree.c (special_function_p): Add sfk_comparison. + * typeck.c (cp_build_binary_op): Handle SPACESHIP_EXPR. + +2019-11-05 Tim van Deurzen + + Add new tree code for the spaceship operator. + * cp-tree.def: Add new tree code. + * operators.def: New binary operator. + * parser.c: Add new token and tree code. + 2019-09-15 Jason Merrill * call.c (build_new_op_1): Don't apply any standard conversions to diff --git a/gcc/cp/call.c b/gcc/cp/call.c index 390a4c581e2..0034c1cee0d 100644 --- a/gcc/cp/call.c +++ b/gcc/cp/call.c @@ -514,6 +514,9 @@ struct z_candidate { /* The flags active in add_candidate. */ int flags; + + bool rewritten () { return (flags & LOOKUP_REWRITTEN); } + bool reversed () { return (flags & LOOKUP_REVERSED); } }; /* Returns true iff T is a null pointer constant in the sense of @@ -2106,6 +2109,11 @@ add_candidate (struct z_candidate **candidates, cand->flags = flags; *candidates = cand; + if (convs && cand->reversed ()) + /* Swap the conversions for comparison in joust; we'll swap them back + before build_over_call. */ + std::swap (convs[0], convs[1]); + return cand; } @@ -2737,6 +2745,16 @@ add_builtin_candidate (struct z_candidate **candidates, enum tree_code code, where LR is the result of the usual arithmetic conversions between types L and R. + For every integral type T there exists a candidate operator function of + the form + + std::strong_ordering operator<=>(T, T); + + For every pair of floating-point types L and R, there exists a candidate + operator function of the form + + std::partial_ordering operator<=>(L, R); + 14For every pair of types T and I, where T is a cv-qualified or cv- unqualified complete object type and I is a promoted integral type, there exist candidate operator functions of the form @@ -2758,11 +2776,15 @@ add_builtin_candidate (struct z_candidate **candidates, enum tree_code code, bool operator>=(T, T); bool operator==(T, T); bool operator!=(T, T); + R operator<=>(T, T); + + where R is the result type specified in [expr.spaceship]. 17For every pointer to member type T, there exist candidate operator functions of the form bool operator==(T, T); - bool operator!=(T, T); */ + bool operator!=(T, T); + std::strong_equality operator<=>(T, T); */ case MINUS_EXPR: if (TYPE_PTROB_P (type1) && TYPE_PTROB_P (type2)) @@ -2780,6 +2802,11 @@ add_builtin_candidate (struct z_candidate **candidates, enum tree_code code, break; return; + /* This isn't exactly what's specified above for operator<=>, but it's + close enough. In particular, we don't care about the return type + specified above; it doesn't participate in overload resolution and it + doesn't affect the semantics of the built-in operator. */ + case SPACESHIP_EXPR: case EQ_EXPR: case NE_EXPR: if ((TYPE_PTRMEMFUNC_P (type1) && TYPE_PTRMEMFUNC_P (type2)) @@ -3138,6 +3165,7 @@ add_builtin_candidates (struct z_candidate **candidates, enum tree_code code, case LE_EXPR: case GT_EXPR: case GE_EXPR: + case SPACESHIP_EXPR: enum_p = 1; /* Fall through. */ @@ -5740,6 +5768,15 @@ add_candidates (tree fns, tree first_arg, const vec *args, fn_args = non_static_args; } + /* Don't bother reversing an operator with two identical parameters. */ + else if (args->length () == 2 && (flags & LOOKUP_REVERSED)) + { + tree parmlist = TYPE_ARG_TYPES (TREE_TYPE (fn)); + if (same_type_p (TREE_VALUE (parmlist), + TREE_VALUE (TREE_CHAIN (parmlist)))) + continue; + } + if (TREE_CODE (fn) == TEMPLATE_DECL) add_template_candidate (candidates, fn, @@ -5800,6 +5837,178 @@ op_is_ordered (tree_code code) } } +/* Subroutine of build_new_op_1: Add to CANDIDATES all candidates for the + operator indicated by CODE/CODE2. This function calls itself recursively to + handle C++20 rewritten comparison operator candidates. */ + +static tree +add_operator_candidates (z_candidate **candidates, + tree_code code, tree_code code2, + vec *arglist, + int flags, tsubst_flags_t complain) +{ + z_candidate *start_candidates = *candidates; + bool ismodop = code2 != ERROR_MARK; + tree fnname = ovl_op_identifier (ismodop, ismodop ? code2 : code); + + /* LOOKUP_REWRITTEN is set when we're looking for the == or <=> operator to + rewrite from, and also when we're looking for the e.g. < operator to use + on the result of <=>. In the latter case, we don't want the flag set in + the candidate, we just want to suppress looking for rewrites. */ + bool rewritten = (flags & LOOKUP_REWRITTEN); + if (rewritten && code != EQ_EXPR && code != SPACESHIP_EXPR) + flags &= ~LOOKUP_REWRITTEN; + + bool memonly = false; + switch (code) + { + /* =, ->, [], () must be non-static member functions. */ + case MODIFY_EXPR: + if (code2 != NOP_EXPR) + break; + /* FALLTHRU */ + case COMPONENT_REF: + case ARRAY_REF: + memonly = true; + break; + + default: + break; + } + + /* Add namespace-scope operators to the list of functions to + consider. */ + if (!memonly) + { + tree fns = lookup_name_real (fnname, 0, 1, /*block_p=*/true, 0, 0); + fns = lookup_arg_dependent (fnname, fns, arglist); + add_candidates (fns, NULL_TREE, arglist, NULL_TREE, + NULL_TREE, false, NULL_TREE, NULL_TREE, + flags, candidates, complain); + } + + /* Add class-member operators to the candidate set. */ + tree arg1_type = TREE_TYPE ((*arglist)[0]); + unsigned nargs = arglist->length () > 1 ? 2 : 1; + tree arg2_type = nargs > 1 ? TREE_TYPE ((*arglist)[1]) : NULL_TREE; + if (CLASS_TYPE_P (arg1_type)) + { + tree fns = lookup_fnfields (arg1_type, fnname, 1); + if (fns == error_mark_node) + return error_mark_node; + if (fns) + add_candidates (BASELINK_FUNCTIONS (fns), + NULL_TREE, arglist, NULL_TREE, + NULL_TREE, false, + BASELINK_BINFO (fns), + BASELINK_ACCESS_BINFO (fns), + flags, candidates, complain); + } + /* Per [over.match.oper]3.2, if no operand has a class type, then + only non-member functions that have type T1 or reference to + cv-qualified-opt T1 for the first argument, if the first argument + has an enumeration type, or T2 or reference to cv-qualified-opt + T2 for the second argument, if the second argument has an + enumeration type. Filter out those that don't match. */ + else if (! arg2_type || ! CLASS_TYPE_P (arg2_type)) + { + struct z_candidate **candp, **next; + + for (candp = candidates; *candp != start_candidates; candp = next) + { + unsigned i; + z_candidate *cand = *candp; + next = &cand->next; + + tree parmlist = TYPE_ARG_TYPES (TREE_TYPE (cand->fn)); + + for (i = 0; i < nargs; ++i) + { + tree parmtype = TREE_VALUE (parmlist); + tree argtype = unlowered_expr_type ((*arglist)[i]); + + if (TYPE_REF_P (parmtype)) + parmtype = TREE_TYPE (parmtype); + if (TREE_CODE (argtype) == ENUMERAL_TYPE + && (same_type_ignoring_top_level_qualifiers_p + (argtype, parmtype))) + break; + + parmlist = TREE_CHAIN (parmlist); + } + + /* No argument has an appropriate type, so remove this + candidate function from the list. */ + if (i == nargs) + { + *candp = cand->next; + next = candp; + } + } + } + + if (!rewritten) + { + /* The standard says to rewrite built-in candidates, too, + but there's no point. */ + add_builtin_candidates (candidates, code, code2, fnname, arglist, + flags, complain); + + /* Maybe add C++20 rewritten comparison candidates. */ + tree_code rewrite_code = ERROR_MARK; + if (cxx_dialect >= cxx2a + && nargs == 2 + && (OVERLOAD_TYPE_P (arg1_type) || OVERLOAD_TYPE_P (arg2_type))) + switch (code) + { + case LT_EXPR: + case LE_EXPR: + case GT_EXPR: + case GE_EXPR: + case SPACESHIP_EXPR: + rewrite_code = SPACESHIP_EXPR; + break; + + case NE_EXPR: + case EQ_EXPR: + rewrite_code = EQ_EXPR; + break; + + default:; + } + + if (rewrite_code) + { + flags |= LOOKUP_REWRITTEN; + if (rewrite_code != code) + /* Add rewritten candidates in same order. */ + add_operator_candidates (candidates, rewrite_code, ERROR_MARK, + arglist, flags, complain); + + z_candidate *save_cand = *candidates; + + /* Add rewritten candidates in reverse order. */ + flags |= LOOKUP_REVERSED; + vec *revlist = make_tree_vector (); + revlist->quick_push ((*arglist)[1]); + revlist->quick_push ((*arglist)[0]); + add_operator_candidates (candidates, rewrite_code, ERROR_MARK, + revlist, flags, complain); + + /* Release the vec if we didn't add a candidate that uses it. */ + for (z_candidate *c = *candidates; c != save_cand; c = c->next) + if (c->args == revlist) + { + revlist = NULL; + break; + } + release_tree_vector (revlist); + } + } + + return NULL_TREE; +} + static tree build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags, tree arg1, tree arg2, tree arg3, tree *overload, @@ -5809,7 +6018,7 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags, vec *arglist; tree result = NULL_TREE; bool result_valid_p = false; - enum tree_code code2 = NOP_EXPR; + enum tree_code code2 = ERROR_MARK; enum tree_code code_orig_arg1 = ERROR_MARK; enum tree_code code_orig_arg2 = ERROR_MARK; conversion *conv; @@ -5828,14 +6037,12 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags, code2 = TREE_CODE (arg3); arg3 = NULL_TREE; } - tree fnname = ovl_op_identifier (ismodop, ismodop ? code2 : code); tree arg1_type = unlowered_expr_type (arg1); tree arg2_type = arg2 ? unlowered_expr_type (arg2) : NULL_TREE; arg1 = prep_operand (arg1); - bool memonly = false; switch (code) { case NEW_EXPR: @@ -5868,16 +6075,6 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags, code_orig_arg2 = TREE_CODE (arg2_type); break; - /* =, ->, [], () must be non-static member functions. */ - case MODIFY_EXPR: - if (code2 != NOP_EXPR) - break; - /* FALLTHRU */ - case COMPONENT_REF: - case ARRAY_REF: - memonly = true; - break; - default: break; } @@ -5908,82 +6105,10 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags, /* Get the high-water mark for the CONVERSION_OBSTACK. */ p = conversion_obstack_alloc (0); - /* Add namespace-scope operators to the list of functions to - consider. */ - if (!memonly) - { - tree fns = lookup_name_real (fnname, 0, 1, /*block_p=*/true, 0, 0); - fns = lookup_arg_dependent (fnname, fns, arglist); - add_candidates (fns, NULL_TREE, arglist, NULL_TREE, - NULL_TREE, false, NULL_TREE, NULL_TREE, - flags, &candidates, complain); - } - - /* Add class-member operators to the candidate set. */ - if (CLASS_TYPE_P (arg1_type)) - { - tree fns; - - fns = lookup_fnfields (arg1_type, fnname, 1); - if (fns == error_mark_node) - { - result = error_mark_node; - goto user_defined_result_ready; - } - if (fns) - add_candidates (BASELINK_FUNCTIONS (fns), - NULL_TREE, arglist, NULL_TREE, - NULL_TREE, false, - BASELINK_BINFO (fns), - BASELINK_ACCESS_BINFO (fns), - flags, &candidates, complain); - } - /* Per [over.match.oper]3.2, if no operand has a class type, then - only non-member functions that have type T1 or reference to - cv-qualified-opt T1 for the first argument, if the first argument - has an enumeration type, or T2 or reference to cv-qualified-opt - T2 for the second argument, if the second argument has an - enumeration type. Filter out those that don't match. */ - else if (! arg2 || ! CLASS_TYPE_P (arg2_type)) - { - struct z_candidate **candp, **next; - - for (candp = &candidates; *candp; candp = next) - { - tree parmlist, parmtype; - int i, nargs = (arg2 ? 2 : 1); - - cand = *candp; - next = &cand->next; - - parmlist = TYPE_ARG_TYPES (TREE_TYPE (cand->fn)); - - for (i = 0; i < nargs; ++i) - { - parmtype = TREE_VALUE (parmlist); - - if (TYPE_REF_P (parmtype)) - parmtype = TREE_TYPE (parmtype); - if (TREE_CODE (unlowered_expr_type ((*arglist)[i])) == ENUMERAL_TYPE - && (same_type_ignoring_top_level_qualifiers_p - (unlowered_expr_type ((*arglist)[i]), parmtype))) - break; - - parmlist = TREE_CHAIN (parmlist); - } - - /* No argument has an appropriate type, so remove this - candidate function from the list. */ - if (i == nargs) - { - *candp = cand->next; - next = candp; - } - } - } - - add_builtin_candidates (&candidates, code, code2, fnname, arglist, - flags, complain); + result = add_operator_candidates (&candidates, code, code2, arglist, + flags, complain); + if (result == error_mark_node) + goto user_defined_result_ready; switch (code) { @@ -6021,6 +6146,7 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags, -fpermissive. */ else { + tree fnname = ovl_op_identifier (ismodop, ismodop ? code2 : code); const char *msg = (flag_permissive) ? G_("no %<%D(int)%> declared for postfix %qs," " trying prefix operator instead") @@ -6091,7 +6217,12 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags, if (resolve_args (arglist, complain) == NULL) result = error_mark_node; else - result = build_over_call (cand, LOOKUP_NORMAL, complain); + { + if (cand->reversed ()) + /* We swapped these in add_candidate, swap them back now. */ + std::swap (cand->convs[0], cand->convs[1]); + result = build_over_call (cand, LOOKUP_NORMAL, complain); + } if (trivial_fn_p (cand->fn)) /* There won't be a CALL_EXPR. */; @@ -6121,6 +6252,73 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags, break; } } + + /* If this was a C++20 rewritten comparison, adjust the result. */ + if (cand->rewritten ()) + { + /* FIXME build_min_non_dep_op_overload can't handle rewrites. */ + if (overload) + *overload = NULL_TREE; + switch (code) + { + case EQ_EXPR: + gcc_checking_assert (cand->reversed ()); + gcc_fallthrough (); + case NE_EXPR: + /* If a rewritten operator== candidate is selected by + overload resolution for an operator @, its return type + shall be cv bool.... */ + if (TREE_CODE (TREE_TYPE (result)) != BOOLEAN_TYPE) + { + if (complain & tf_error) + { + auto_diagnostic_group d; + error_at (loc, "return type of %qD is not %qs", + cand->fn, "bool"); + inform (loc, "used as rewritten candidate for " + "comparison of %qT and %qT", + arg1_type, arg2_type); + } + result = error_mark_node; + } + else if (code == NE_EXPR) + /* !(y == x) or !(x == y) */ + result = build1_loc (loc, TRUTH_NOT_EXPR, + boolean_type_node, result); + break; + + /* If a rewritten operator<=> candidate is selected by + overload resolution for an operator @, x @ y is + interpreted as 0 @ (y <=> x) if the selected candidate is + a synthesized candidate with reversed order of parameters, + or (x <=> y) @ 0 otherwise, using the selected rewritten + operator<=> candidate. */ + case SPACESHIP_EXPR: + if (!cand->reversed ()) + /* We're in the build_new_op call below for an outer + reversed call; we don't need to do anything more. */ + break; + gcc_fallthrough (); + case LT_EXPR: + case LE_EXPR: + case GT_EXPR: + case GE_EXPR: + { + tree lhs = result; + tree rhs = integer_zero_node; + if (cand->reversed ()) + std::swap (lhs, rhs); + result = build_new_op (loc, code, + LOOKUP_NORMAL|LOOKUP_REWRITTEN, + lhs, rhs, NULL_TREE, + NULL, complain); + } + break; + + default: + gcc_unreachable (); + } + } } else { @@ -6232,6 +6430,7 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags, if (complain & tf_warning && warn_tautological_compare) warn_tautological_cmp (loc, code, arg1, arg2); /* Fall through. */ + case SPACESHIP_EXPR: case PLUS_EXPR: case MINUS_EXPR: case MULT_EXPR: @@ -6307,6 +6506,29 @@ extract_call_expr (tree call) call = TREE_OPERAND (call, 0); if (TREE_CODE (call) == TARGET_EXPR) call = TARGET_EXPR_INITIAL (call); + if (cxx_dialect >= cxx2a) + switch (TREE_CODE (call)) + { + /* C++20 rewritten comparison operators. */ + case TRUTH_NOT_EXPR: + call = TREE_OPERAND (call, 0); + break; + case LT_EXPR: + case LE_EXPR: + case GT_EXPR: + case GE_EXPR: + case SPACESHIP_EXPR: + { + tree op0 = TREE_OPERAND (call, 0); + if (integer_zerop (op0)) + call = TREE_OPERAND (call, 1); + else + call = op0; + } + break; + default:; + } + gcc_assert (TREE_CODE (call) == CALL_EXPR || TREE_CODE (call) == AGGR_INIT_EXPR || call == error_mark_node); @@ -10772,6 +10994,20 @@ joust_maybe_elide_copy (z_candidate *&cand) return false; } +/* True if cand1 and cand2 represent the same function or function + template. */ + +static bool +same_fn_or_template (z_candidate *cand1, z_candidate *cand2) +{ + if (cand1->fn == cand2->fn) + return true; + if (!cand1->template_decl || !cand2->template_decl) + return false; + return (most_general_template (TI_TEMPLATE (cand1->template_decl)) + == most_general_template (TI_TEMPLATE (cand2->template_decl))); +} + /* Compare two candidates for overloading as described in [over.match.best]. Return values: @@ -10798,6 +11034,7 @@ joust (struct z_candidate *cand1, struct z_candidate *cand2, bool warn, /* If we have two pseudo-candidates for conversions to the same type, or two candidates for the same function, arbitrarily pick one. */ if (cand1->fn == cand2->fn + && cand1->reversed () == cand2->reversed () && (IS_TYPE_OR_DECL_P (cand1->fn))) return 1; @@ -10917,6 +11154,21 @@ joust (struct z_candidate *cand1, struct z_candidate *cand2, bool warn, if (winner && comp != winner) { + if (same_fn_or_template (cand1, cand2)) + { + /* Ambiguity between normal and reversed versions of the + same comparison operator; prefer the normal one. + https://lists.isocpp.org/core/2019/10/7438.php */ + if (cand1->reversed ()) + winner = -1; + else + { + gcc_checking_assert (cand2->reversed ()); + winner = 1; + } + break; + } + winner = 0; goto tweak; } @@ -11046,6 +11298,21 @@ joust (struct z_candidate *cand1, struct z_candidate *cand2, bool warn, return winner; } + /* F2 is a rewritten candidate (12.4.1.2) and F1 is not, or F1 and F2 are + rewritten candidates, and F2 is a synthesized candidate with reversed + order of parameters and F1 is not. */ + if (cand1->rewritten ()) + { + if (!cand2->rewritten ()) + return -1; + if (!cand1->reversed () && cand2->reversed ()) + return 1; + if (cand1->reversed () && !cand2->reversed ()) + return -1; + } + else if (cand2->rewritten ()) + return 1; + /* F1 is generated from a deduction-guide (13.3.1.8) and F2 is not */ if (deduction_guide_p (cand1->fn)) { diff --git a/gcc/cp/class.c b/gcc/cp/class.c index 35727074156..89ed1c040f6 100644 --- a/gcc/cp/class.c +++ b/gcc/cp/class.c @@ -3234,6 +3234,17 @@ add_implicitly_declared_members (tree t, tree* access_decls, a virtual function from a base class. */ declare_virt_assop_and_dtor (t); + /* If the class definition does not explicitly declare an == operator + function, but declares a defaulted three-way comparison operator function, + an == operator function is declared implicitly. */ + if (!classtype_has_op (t, EQ_EXPR)) + if (tree space = classtype_has_defaulted_op (t, SPACESHIP_EXPR)) + { + tree eq = implicitly_declare_fn (sfk_comparison, t, false, space, + NULL_TREE); + add_method (t, eq, false); + } + while (*access_decls) { tree using_decl = TREE_VALUE (*access_decls); @@ -5386,6 +5397,44 @@ classtype_has_depr_implicit_copy (tree t) return NULL_TREE; } +/* True iff T has a member or friend declaration of operator OP. */ + +bool +classtype_has_op (tree t, tree_code op) +{ + tree name = ovl_op_identifier (op); + if (get_class_binding (t, name)) + return true; + for (tree f = DECL_FRIENDLIST (TYPE_MAIN_DECL (t)); f; f = TREE_CHAIN (f)) + if (FRIEND_NAME (f) == name) + return true; + return false; +} + + +/* If T has a defaulted member or friend declaration of OP, return it. */ + +tree +classtype_has_defaulted_op (tree t, tree_code op) +{ + tree name = ovl_op_identifier (op); + for (ovl_iterator oi (get_class_binding (t, name)); oi; ++oi) + { + tree fn = *oi; + if (DECL_DEFAULTED_FN (fn)) + return fn; + } + for (tree f = DECL_FRIENDLIST (TYPE_MAIN_DECL (t)); f; f = TREE_CHAIN (f)) + if (FRIEND_NAME (f) == name) + for (tree l = FRIEND_DECLS (f); l; l = TREE_CHAIN (l)) + { + tree fn = TREE_VALUE (l); + if (DECL_DEFAULTED_FN (fn)) + return fn; + } + return NULL_TREE; +} + /* Nonzero if we need to build up a constructor call when initializing an object of this class, either because it has a user-declared constructor or because it doesn't have a default constructor (so we need to give an diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index ce910cd5a3b..20fddc57825 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -2480,6 +2480,12 @@ cxx_eval_binary_expression (const constexpr_ctx *ctx, tree t, else if (code == POINTER_PLUS_EXPR) r = cxx_fold_pointer_plus_expression (ctx, t, lhs, rhs, non_constant_p, overflow_p); + else if (code == SPACESHIP_EXPR) + { + r = genericize_spaceship (type, lhs, rhs); + r = cxx_eval_constant_expression (ctx, r, false, non_constant_p, + overflow_p); + } if (r == NULL_TREE) r = fold_binary_loc (loc, code, type, lhs, rhs); @@ -5226,6 +5232,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, case GE_EXPR: case EQ_EXPR: case NE_EXPR: + case SPACESHIP_EXPR: case UNORDERED_EXPR: case ORDERED_EXPR: case UNLT_EXPR: @@ -7037,6 +7044,7 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, case GE_EXPR: case EQ_EXPR: case NE_EXPR: + case SPACESHIP_EXPR: want_rval = true; goto binary; diff --git a/gcc/cp/cp-gimplify.c b/gcc/cp/cp-gimplify.c index abd82b3fe42..744707e792d 100644 --- a/gcc/cp/cp-gimplify.c +++ b/gcc/cp/cp-gimplify.c @@ -1144,6 +1144,17 @@ cp_fold_function (tree fndecl) cp_walk_tree (&DECL_SAVED_TREE (fndecl), cp_fold_r, &pset, NULL); } +/* Turn SPACESHIP_EXPR EXPR into GENERIC. */ + +static tree genericize_spaceship (tree expr) +{ + iloc_sentinel s (cp_expr_location (expr)); + tree type = TREE_TYPE (expr); + tree op0 = TREE_OPERAND (expr, 0); + tree op1 = TREE_OPERAND (expr, 1); + return genericize_spaceship (type, op0, op1); +} + /* Perform any pre-gimplification lowering of C++ front end trees to GENERIC. */ @@ -1574,6 +1585,10 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data) genericize_break_stmt (stmt_p); break; + case SPACESHIP_EXPR: + *stmt_p = genericize_spaceship (*stmt_p); + break; + case OMP_FOR: case OMP_SIMD: case OMP_DISTRIBUTE: diff --git a/gcc/cp/cp-objcp-common.c b/gcc/cp/cp-objcp-common.c index 60dcbe44105..b9bc2c6592c 100644 --- a/gcc/cp/cp-objcp-common.c +++ b/gcc/cp/cp-objcp-common.c @@ -518,6 +518,7 @@ cp_common_init_ts (void) MARK_TS_EXP (VEC_DELETE_EXPR); MARK_TS_EXP (VEC_INIT_EXPR); MARK_TS_EXP (VEC_NEW_EXPR); + MARK_TS_EXP (SPACESHIP_EXPR); /* Fold expressions. */ MARK_TS_EXP (BINARY_LEFT_FOLD_EXPR); diff --git a/gcc/cp/cp-tree.def b/gcc/cp/cp-tree.def index 845a7e251f3..4e798e3b614 100644 --- a/gcc/cp/cp-tree.def +++ b/gcc/cp/cp-tree.def @@ -255,6 +255,7 @@ DEFTREECODE (IMPLICIT_CONV_EXPR, "implicit_conv_expr", tcc_unary, 1) DEFTREECODE (DOTSTAR_EXPR, "dotstar_expr", tcc_expression, 2) DEFTREECODE (TYPEID_EXPR, "typeid_expr", tcc_expression, 1) DEFTREECODE (NOEXCEPT_EXPR, "noexcept_expr", tcc_unary, 1) +DEFTREECODE (SPACESHIP_EXPR, "spaceship_expr", tcc_expression, 2) /* A placeholder for an expression that is not type-dependent, but does occur in a template. When an expression that is not diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 58d7d016197..2b45d62ce21 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -2695,7 +2695,8 @@ struct GTY(()) lang_decl_fn { unsigned omp_declare_reduction_p : 1; unsigned has_dependent_explicit_spec_p : 1; unsigned immediate_fn_p : 1; - unsigned spare : 11; + unsigned maybe_deleted : 1; + unsigned spare : 10; /* 32-bits padding on 64-bit host. */ @@ -3137,6 +3138,11 @@ struct GTY(()) lang_decl { #define DECL_HAS_DEPENDENT_EXPLICIT_SPEC_P(NODE) \ (LANG_DECL_FN_CHECK (NODE)->has_dependent_explicit_spec_p) +/* Nonzero for a defaulted FUNCTION_DECL for which we haven't decided yet if + it's deleted. */ +#define DECL_MAYBE_DELETED(NODE) \ + (LANG_DECL_FN_CHECK (NODE)->maybe_deleted) + /* True (in a FUNCTION_DECL) if NODE is a virtual function that is an invalid overrider for a function from a base class. Once we have complained about an invalid overrider we avoid complaining about it @@ -5203,6 +5209,7 @@ enum special_function_kind { destroyed. */ sfk_conversion, /* A conversion operator. */ sfk_deduction_guide, /* A class template deduction guide. */ + sfk_comparison, /* A comparison operator (e.g. ==, <, <=>). */ sfk_virtual_destructor /* Used by member synthesis fns. */ }; @@ -5565,6 +5572,17 @@ enum overload_flags { NO_SPECIAL = 0, DTOR_FLAG, TYPENAME_FLAG }; #define LOOKUP_ALLOW_FLEXARRAY_INIT (LOOKUP_DELEGATING_CONS << 1) /* Require constant initialization of a non-constant variable. */ #define LOOKUP_CONSTINIT (LOOKUP_ALLOW_FLEXARRAY_INIT << 1) +/* We're looking for either a rewritten comparison operator candidate or the + operator to use on the former's result. We distinguish between the two by + knowing that comparisons other than == and <=> must be the latter, as must + a <=> expression trying to rewrite to <=> without reversing. */ +#define LOOKUP_REWRITTEN (LOOKUP_CONSTINIT << 1) +/* Reverse the order of the two arguments for comparison rewriting. First we + swap the arguments in add_operator_candidates, then we swap the conversions + in add_candidate (so that they correspond to the original order of the + args), then we swap the conversions back in build_new_op_1 (so they + correspond to the order of the args in the candidate). */ +#define LOOKUP_REVERSED (LOOKUP_REWRITTEN << 1) #define LOOKUP_NAMESPACES_ONLY(F) \ (((F) & LOOKUP_PREFER_NAMESPACES) && !((F) & LOOKUP_PREFER_TYPES)) @@ -6371,6 +6389,8 @@ extern bool type_has_virtual_destructor (tree); extern bool classtype_has_move_assign_or_move_ctor_p (tree, bool user_declared); extern bool classtype_has_non_deleted_move_ctor (tree); extern tree classtype_has_depr_implicit_copy (tree); +extern bool classtype_has_op (tree, tree_code); +extern tree classtype_has_defaulted_op (tree, tree_code); extern bool type_build_ctor_call (tree); extern bool type_build_dtor_call (tree); extern void explain_non_literal_class (tree); @@ -7544,6 +7564,8 @@ extern tree composite_pointer_type (const op_location_t &, extern tree merge_types (tree, tree); extern tree strip_array_domain (tree); extern tree check_return_expr (tree, bool *); +extern tree spaceship_type (tree, tsubst_flags_t = tf_warning_or_error); +extern tree genericize_spaceship (tree, tree, tree); extern tree cp_build_binary_op (const op_location_t &, enum tree_code, tree, tree, tsubst_flags_t); diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c index 07112aad092..3bfcfb2c6b7 100644 --- a/gcc/cp/decl.c +++ b/gcc/cp/decl.c @@ -16793,6 +16793,13 @@ finish_function (bool inline_p) } } + if (DECL_DELETED_FN (fndecl)) + { + DECL_INITIAL (fndecl) = error_mark_node; + DECL_SAVED_TREE (fndecl) = NULL_TREE; + goto cleanup; + } + // If this is a concept, check that the definition is reasonable. if (DECL_DECLARED_CONCEPT_P (fndecl)) check_function_concept (fndecl); @@ -16939,6 +16946,7 @@ finish_function (bool inline_p) if (!processing_template_decl && !DECL_IMMEDIATE_FUNCTION_P (fndecl)) cp_genericize (fndecl); + cleanup: /* We're leaving the context of this function, so zap cfun. It's still in DECL_STRUCT_FUNCTION, and we'll restore it in tree_rest_of_compilation. */ set_cfun (NULL); diff --git a/gcc/cp/decl2.c b/gcc/cp/decl2.c index 4dc54811122..4f0b2161db1 100644 --- a/gcc/cp/decl2.c +++ b/gcc/cp/decl2.c @@ -927,6 +927,10 @@ grokfield (const cp_declarator *declarator, } else if (init == ridpointers[(int)RID_DEFAULT]) { + if (friendp) + /* ??? do_friend doesn't set this because funcdef_flag is false + for in-class defaulted functions. So set it here. */ + SET_DECL_FRIEND_CONTEXT (value, current_class_type); if (defaultable_fn_check (value)) { DECL_DEFAULTED_FN (value) = 1; @@ -5471,6 +5475,17 @@ mark_used (tree decl, tsubst_flags_t complain) if (TREE_CODE (decl) == CONST_DECL) used_types_insert (DECL_CONTEXT (decl)); + if (TREE_CODE (decl) == FUNCTION_DECL + && DECL_MAYBE_DELETED (decl)) + { + /* ??? Switch other defaulted functions to use DECL_MAYBE_DELETED? */ + gcc_assert (special_function_p (decl) == sfk_comparison); + + ++function_depth; + synthesize_method (decl); + --function_depth; + } + if (TREE_CODE (decl) == FUNCTION_DECL && !maybe_instantiate_noexcept (decl, complain)) return false; @@ -5592,7 +5607,6 @@ mark_used (tree decl, tsubst_flags_t complain) /* Is it a synthesized method that needs to be synthesized? */ if (TREE_CODE (decl) == FUNCTION_DECL - && DECL_NONSTATIC_MEMBER_FUNCTION_P (decl) && DECL_DEFAULTED_FN (decl) /* A function defaulted outside the class is synthesized either by cp_finish_decl or instantiate_decl. */ diff --git a/gcc/cp/error.c b/gcc/cp/error.c index d104a4d574c..c06776f565a 100644 --- a/gcc/cp/error.c +++ b/gcc/cp/error.c @@ -2286,6 +2286,7 @@ dump_expr (cxx_pretty_printer *pp, tree t, int flags) case GE_EXPR: case EQ_EXPR: case NE_EXPR: + case SPACESHIP_EXPR: case EXACT_DIV_EXPR: dump_binary_op (pp, OVL_OP_INFO (false, TREE_CODE (t))->name, t, flags); break; diff --git a/gcc/cp/method.c b/gcc/cp/method.c index 09e9c73cda5..c9dd90fcba7 100644 --- a/gcc/cp/method.c +++ b/gcc/cp/method.c @@ -406,6 +406,7 @@ type_has_trivial_fn (tree ctype, special_function_kind sfk) case sfk_virtual_destructor: return !TYPE_HAS_NONTRIVIAL_DESTRUCTOR (ctype); case sfk_inheriting_constructor: + case sfk_comparison: return false; default: gcc_unreachable (); @@ -877,6 +878,588 @@ do_build_copy_assign (tree fndecl) finish_compound_stmt (compound_stmt); } +/* C++20 comparison category types. */ + +enum comp_cat_tag +{ + cc_weak_equality, + cc_strong_equality, + cc_partial_ordering, + cc_weak_ordering, + cc_strong_ordering, + cc_last +}; + +/* Names of the comparison categories and their value members, to be indexed by + comp_cat_tag enumerators. genericize_spaceship below relies on the ordering + of the members. */ + +struct comp_cat_info_t +{ + const char *name; + const char *members[4]; +}; +static const comp_cat_info_t comp_cat_info[cc_last] += { + { "weak_equality", "equivalent", "nonequivalent" }, + { "strong_equality", "equal", "nonequal" }, + { "partial_ordering", "equivalent", "greater", "less", "unordered" }, + { "weak_ordering", "equivalent", "greater", "less" }, + { "strong_ordering", "equal", "greater", "less" } +}; + +/* A cache of the category types to speed repeated lookups. */ + +static GTY((deletable)) tree comp_cat_cache[cc_last]; + +/* Look up one of the result variables in the comparison category type. */ + +static tree +lookup_comparison_result (tree type, const char *name_str, + tsubst_flags_t complain = tf_warning_or_error) +{ + tree name = get_identifier (name_str); + tree decl = lookup_qualified_name (type, name); + if (TREE_CODE (decl) != VAR_DECL) + { + if (complain & tf_error) + { + auto_diagnostic_group d; + if (decl == error_mark_node || TREE_CODE (decl) == TREE_LIST) + qualified_name_lookup_error (type, name, decl, input_location); + else + error ("%<%T::%D%> is not a static data member", type, decl); + inform (input_location, "determining value of %qs", "operator<=>"); + } + return error_mark_node; + } + return decl; +} + +/* Look up a comparison category type in std. */ + +static tree +lookup_comparison_category (comp_cat_tag tag, + tsubst_flags_t complain = tf_warning_or_error) +{ + if (tree cached = comp_cat_cache[tag]) + return cached; + + tree name = get_identifier (comp_cat_info[tag].name); + tree decl = lookup_qualified_name (std_node, name); + if (TREE_CODE (decl) != TYPE_DECL) + { + if (complain & tf_error) + { + auto_diagnostic_group d; + if (decl == error_mark_node || TREE_CODE (decl) == TREE_LIST) + qualified_name_lookup_error (std_node, name, decl, input_location); + else + error ("% is not a type", decl); + inform (input_location, "forming type of %qs", "operator<=>"); + } + return error_mark_node; + } + /* Also make sure we can look up the value members now, since we won't + really use them until genericize time. */ + tree type = TREE_TYPE (decl); + for (int i = 0; i < 4; ++i) + { + const char *p = comp_cat_info[tag].members[i]; + if (!p) break; + if (lookup_comparison_result (type, p, complain) + == error_mark_node) + return error_mark_node; + } + return comp_cat_cache[tag] = type; +} + +/* Wrapper that takes the tag rather than the type. */ + +static tree +lookup_comparison_result (comp_cat_tag tag, const char *name_str, + tsubst_flags_t complain = tf_warning_or_error) +{ + tree type = lookup_comparison_category (tag, complain); + return lookup_comparison_result (type, name_str, complain); +} + +/* Wrapper that takes the index into the members array instead of the name. */ + +static tree +lookup_comparison_result (comp_cat_tag tag, tree type, int idx) +{ + const char *name_str = comp_cat_info[tag].members[idx]; + if (!name_str) + return NULL_TREE; + return lookup_comparison_result (type, name_str); +} + +/* Does TYPE correspond to TAG? */ + +static bool +is_cat (tree type, comp_cat_tag tag) +{ + tree name = TYPE_LINKAGE_IDENTIFIER (type); + return id_equal (name, comp_cat_info[tag].name); +} + +/* Return the comp_cat_tag for TYPE. */ + +static comp_cat_tag +cat_tag_for (tree type) +{ + for (int i = 0; i < cc_last; ++i) + { + comp_cat_tag tag = (comp_cat_tag)i; + if (is_cat (type, tag)) + return tag; + } + return cc_last; +} + +/* Return the comparison category tag of a <=> expression with non-class type + OPTYPE. */ + +static comp_cat_tag +spaceship_comp_cat (tree optype) +{ + if (INTEGRAL_OR_ENUMERATION_TYPE_P (optype) || TYPE_PTROBV_P (optype)) + return cc_strong_ordering; + else if (TREE_CODE (optype) == REAL_TYPE) + return cc_partial_ordering; + else if (TYPE_PTRFN_P (optype) || TYPE_PTRMEM_P (optype) + || NULLPTR_TYPE_P (optype)) + return cc_strong_equality; + else if (TREE_CODE (optype) == COMPLEX_TYPE) + { + tree intype = optype; + while (TREE_CODE (intype) == COMPLEX_TYPE) + intype = TREE_TYPE (intype); + if (TREE_CODE (intype) == REAL_TYPE) + return cc_weak_equality; + else + return cc_strong_equality; + } + + /* FIXME should vector <=> produce a vector of one of the above? */ + gcc_unreachable (); +} + +/* Return the comparison category type of a <=> expression with non-class type + OPTYPE. */ + +tree +spaceship_type (tree optype, tsubst_flags_t complain) +{ + comp_cat_tag tag = spaceship_comp_cat (optype); + return lookup_comparison_category (tag, complain); +} + +/* Turn <=> with type TYPE and operands OP0 and OP1 into GENERIC. */ + +tree +genericize_spaceship (tree type, tree op0, tree op1) +{ + /* ??? maybe optimize based on knowledge of representation? */ + comp_cat_tag tag = cat_tag_for (type); + gcc_checking_assert (tag < cc_last); + + tree eq = lookup_comparison_result (tag, type, 0); + tree negt = lookup_comparison_result (tag, type, 1); + + if (tag == cc_strong_equality || tag == cc_weak_equality) + { + tree comp = fold_build2 (EQ_EXPR, boolean_type_node, op0, op1); + return fold_build3 (COND_EXPR, type, comp, eq, negt); + } + + tree r; + op0 = save_expr (op0); + op1 = save_expr (op1); + + if (tag == cc_partial_ordering) + { + /* op0 == op1 ? equivalent : op0 < op1 ? less : + op0 > op1 ? greater : unordered */ + tree uo = lookup_comparison_result (tag, type, 3); + tree comp = fold_build2 (GT_EXPR, boolean_type_node, op0, op1); + r = fold_build3 (COND_EXPR, type, comp, negt, uo); + } + else + /* op0 == op1 ? equal : op0 < op1 ? less : greater */ + r = negt; + + tree lt = lookup_comparison_result (tag, type, 2); + tree comp = fold_build2 (LT_EXPR, boolean_type_node, op0, op1); + r = fold_build3 (COND_EXPR, type, comp, lt, r); + + comp = fold_build2 (EQ_EXPR, boolean_type_node, op0, op1); + r = fold_build3 (COND_EXPR, type, comp, eq, r); + + return r; +} + +/* Check that the signature of a defaulted comparison operator is + well-formed. */ + +static bool +early_check_defaulted_comparison (tree fn) +{ + location_t loc = DECL_SOURCE_LOCATION (fn); + tree ctx; + if (DECL_CLASS_SCOPE_P (fn)) + ctx = DECL_CONTEXT (fn); + else + ctx = DECL_FRIEND_CONTEXT (fn); + bool ok = true; + + if (!DECL_OVERLOADED_OPERATOR_IS (fn, SPACESHIP_EXPR) + && !same_type_p (TREE_TYPE (TREE_TYPE (fn)), boolean_type_node)) + { + error_at (loc, "defaulted %qD must return %", fn); + ok = false; + } + + int i = DECL_NONSTATIC_MEMBER_FUNCTION_P (fn); + if (i && type_memfn_quals (TREE_TYPE (fn)) != TYPE_QUAL_CONST) + { + error_at (loc, "defaulted %qD must be %", fn); + ok = false; + } + tree parmnode = FUNCTION_FIRST_USER_PARMTYPE (fn); + for (; parmnode != void_list_node; parmnode = TREE_CHAIN (parmnode)) + { + ++i; + tree parmtype = TREE_VALUE (parmnode); + diagnostic_t kind = DK_UNSPECIFIED; + int opt = 0; + if (same_type_p (parmtype, ctx)) + /* The draft specifies const reference, but let's also allow by-value + unless -Wpedantic, hopefully it will be added soon. */ + kind = DK_PEDWARN, + opt = OPT_Wpedantic; + else if (TREE_CODE (parmtype) != REFERENCE_TYPE + || TYPE_QUALS (TREE_TYPE (parmtype)) != TYPE_QUAL_CONST + || !(same_type_ignoring_top_level_qualifiers_p + (TREE_TYPE (parmtype), ctx))) + kind = DK_ERROR; + if (kind) + emit_diagnostic (kind, loc, opt, "defaulted %qD must have " + "parameter type %", fn, ctx); + if (kind == DK_ERROR) + ok = false; + } + + /* We still need to deduce deleted/constexpr/noexcept and maybe return. */ + DECL_MAYBE_DELETED (fn) = true; + + return ok; +} + +/* Subroutine of build_comparison_op. Given the vec of memberwise + comparisons COMPS, calculate the overall comparison category for + operator<=>. */ + +static tree +common_comparison_type (vec &comps) +{ + tree seen[cc_last] = {}; + + for (unsigned i = 0; i < comps.length(); ++i) + { + tree comp = comps[i]; + tree ctype = TREE_TYPE (comp); + comp_cat_tag tag = cat_tag_for (ctype); + if (tag < cc_last) + seen[tag] = ctype; + else + /* If any Ti is not a comparison category type, U is void. */ + return void_type_node; + } + + /* Otherwise, if at least one T i is std::weak_equality, or at least one T i + is std::strong_equality and at least one T j is std::partial_ordering or + std::weak_ordering, U is std::weak_equality. */ + if (tree t = seen[cc_weak_equality]) return t; + if (seen[cc_strong_equality] + && (seen[cc_partial_ordering] || seen[cc_weak_ordering])) + return lookup_comparison_category (cc_weak_equality); + + /* Otherwise, if at least one T i is std::strong_equality, U is + std::strong_equality. */ + if (tree t = seen[cc_strong_equality]) return t; + + /* Otherwise, if at least one T i is std::partial_ordering, U is + std::partial_ordering. */ + if (tree t = seen[cc_partial_ordering]) return t; + + /* Otherwise, if at least one T i is std::weak_ordering, U is + std::weak_ordering. */ + if (tree t = seen[cc_weak_ordering]) return t; + + /* Otherwise, U is std::strong_ordering. */ + if (tree t = seen[cc_strong_ordering]) return t; + return lookup_comparison_category (cc_strong_ordering); +} + +/* Data structure for build_comparison_op. */ + +struct comp_info +{ + tree fndecl; + location_t loc; + bool defining; + bool first_time; + bool constexp; + bool was_constexp; + bool noex; + + comp_info (tree fndecl, tsubst_flags_t &complain) + : fndecl (fndecl) + { + loc = DECL_SOURCE_LOCATION (fndecl); + + /* We only have tf_error set when we're called from + explain_invalid_constexpr_fn or maybe_explain_implicit_delete. */ + defining = !(complain & tf_error); + + first_time = DECL_MAYBE_DELETED (fndecl); + DECL_MAYBE_DELETED (fndecl) = false; + + /* Do we want to try to set constexpr? */ + was_constexp = DECL_DECLARED_CONSTEXPR_P (fndecl); + constexp = first_time; + if (constexp) + /* Set this for var_in_constexpr_fn. */ + DECL_DECLARED_CONSTEXPR_P (fndecl) = true; + + /* Do we want to try to set noexcept? */ + noex = first_time; + if (noex) + { + tree raises = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fndecl)); + if (raises && !UNEVALUATED_NOEXCEPT_SPEC_P (raises)) + /* There was an explicit exception-specification. */ + noex = false; + } + } + + /* EXPR is an expression built as part of the function body. + Adjust the properties appropriately. */ + void check (tree expr) + { + if (expr == error_mark_node) + DECL_DELETED_FN (fndecl) = true; + if ((constexp || was_constexp) + && !potential_rvalue_constant_expression (expr)) + { + if (was_constexp) + require_potential_rvalue_constant_expression (expr); + else + constexp = false; + } + if (noex && !expr_noexcept_p (expr, tf_none)) + noex = false; + } +}; + +/* Build up the definition of a defaulted comparison operator. Unlike other + defaulted functions that use synthesized_method_walk to determine whether + the function is e.g. deleted, for comparisons we use the same code. We try + to use synthesize_method at the earliest opportunity and bail out if the + function ends up being deleted. */ + +static void +build_comparison_op (tree fndecl, tsubst_flags_t complain) +{ + comp_info info (fndecl, complain); + + if (!info.defining && !(complain & tf_error) && !DECL_MAYBE_DELETED (fndecl)) + return; + + int flags = LOOKUP_NORMAL | LOOKUP_NONVIRTUAL | LOOKUP_DEFAULTED; + const ovl_op_info_t *op = IDENTIFIER_OVL_OP_INFO (DECL_NAME (fndecl)); + tree_code code = op->tree_code; + + tree lhs = DECL_ARGUMENTS (fndecl); + tree rhs = DECL_CHAIN (lhs); + if (is_this_parameter (lhs)) + lhs = cp_build_fold_indirect_ref (lhs); + else + lhs = convert_from_reference (lhs); + rhs = convert_from_reference (rhs); + tree ctype = TYPE_MAIN_VARIANT (TREE_TYPE (lhs)); + + iloc_sentinel ils (info.loc); + + /* A defaulted comparison operator function for class C is defined as + deleted if ... C is a union-like class. */ + if (TREE_CODE (ctype) == UNION_TYPE) + { + if (complain & tf_error) + inform (info.loc, "cannot default compare union %qT", ctype); + DECL_DELETED_FN (fndecl) = true; + } + + tree compound_stmt = NULL_TREE; + if (info.defining) + compound_stmt = begin_compound_stmt (0); + else + ++cp_unevaluated_operand; + + tree rettype = TREE_TYPE (TREE_TYPE (fndecl)); + if (code != SPACESHIP_EXPR && is_auto (rettype)) + { + rettype = boolean_type_node; + apply_deduced_return_type (fndecl, rettype); + } + + if (code == EQ_EXPR || code == SPACESHIP_EXPR) + { + auto_vec comps; + + /* Compare each of the subobjects. Note that we get bases from + next_initializable_field because we're past C++17. */ + for (tree field = next_initializable_field (TYPE_FIELDS (ctype)); + field; + field = next_initializable_field (DECL_CHAIN (field))) + { + tree expr_type = TREE_TYPE (field); + + /* A defaulted comparison operator function for class C is defined as + deleted if any non-static data member of C is of reference type or + C is a union-like class. */ + if (TREE_CODE (expr_type) == REFERENCE_TYPE) + { + if (complain & tf_error) + inform (DECL_SOURCE_LOCATION (field), "cannot default compare " + "reference member %qD", field); + DECL_DELETED_FN (fndecl) = true; + continue; + } + else if (ANON_UNION_TYPE_P (expr_type)) + { + if (complain & tf_error) + inform (DECL_SOURCE_LOCATION (field), "cannot default compare " + "anonymous union member"); + DECL_DELETED_FN (fndecl) = true; + continue; + } + + tree lhs_mem = build3 (COMPONENT_REF, expr_type, lhs, field, + NULL_TREE); + tree rhs_mem = build3 (COMPONENT_REF, expr_type, rhs, field, + NULL_TREE); + tree comp = build_new_op (info.loc, code, flags, lhs_mem, rhs_mem, + NULL_TREE, NULL, complain); + comps.safe_push (comp); + } + if (code == SPACESHIP_EXPR && is_auto (rettype)) + { + rettype = common_comparison_type (comps); + apply_deduced_return_type (fndecl, rettype); + } + for (unsigned i = 0; i < comps.length(); ++i) + { + tree comp = comps[i]; + tree eq, retval = NULL_TREE, if_ = NULL_TREE; + if (info.defining) + if_ = begin_if_stmt (); + /* Spaceship is specified to use !=, but for the comparison category + types, != is equivalent to !(==), so let's use == directly. */ + if (code == EQ_EXPR) + { + /* if (x==y); else return false; */ + eq = comp; + retval = boolean_false_node; + } + else + { + /* if (auto v = x<=>y, v == 0); else return v; */ + if (TREE_CODE (comp) == SPACESHIP_EXPR) + TREE_TYPE (comp) = rettype; + else + comp = build_static_cast (rettype, comp, complain); + info.check (comp); + if (info.defining) + { + tree var = create_temporary_var (rettype); + pushdecl (var); + cp_finish_decl (var, comp, false, NULL_TREE, flags); + comp = retval = var; + } + eq = build_new_op (info.loc, EQ_EXPR, flags, comp, + integer_zero_node, NULL_TREE, NULL, + complain); + } + tree ceq = contextual_conv_bool (eq, complain); + info.check (ceq); + if (info.defining) + { + finish_if_stmt_cond (ceq, if_); + finish_then_clause (if_); + begin_else_clause (if_); + finish_return_stmt (retval); + finish_else_clause (if_); + finish_if_stmt (if_); + } + } + if (info.defining) + { + tree val; + if (code == EQ_EXPR) + val = boolean_true_node; + else + { + tree seql = lookup_comparison_result (cc_strong_ordering, + "equal", complain); + val = build_static_cast (rettype, seql, complain); + } + finish_return_stmt (val); + } + } + else if (code == NE_EXPR) + { + tree comp = build_new_op (info.loc, EQ_EXPR, flags, lhs, rhs, + NULL_TREE, NULL, complain); + comp = contextual_conv_bool (comp, complain); + info.check (comp); + if (info.defining) + { + tree neg = build1 (TRUTH_NOT_EXPR, boolean_type_node, comp); + finish_return_stmt (neg); + } + } + else + { + tree comp = build_new_op (info.loc, SPACESHIP_EXPR, flags, lhs, rhs, + NULL_TREE, NULL, complain); + tree comp2 = build_new_op (info.loc, code, flags, comp, integer_zero_node, + NULL_TREE, NULL, complain); + info.check (comp2); + if (info.defining) + finish_return_stmt (comp2); + } + + if (info.defining) + finish_compound_stmt (compound_stmt); + else + --cp_unevaluated_operand; + + if (info.first_time) + { + DECL_DECLARED_CONSTEXPR_P (fndecl) = info.constexp || info.was_constexp; + tree raises = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fndecl)); + if (!raises || UNEVALUATED_NOEXCEPT_SPEC_P (raises)) + { + raises = info.noex ? noexcept_true_spec : noexcept_false_spec; + TREE_TYPE (fndecl) = build_exception_variant (TREE_TYPE (fndecl), + raises); + } + } +} + /* Synthesize FNDECL, a non-static member function. */ void @@ -889,6 +1472,7 @@ synthesize_method (tree fndecl) location_t save_input_location = input_location; int error_count = errorcount; int warning_count = warningcount + werrorcount; + special_function_kind sfk = special_function_p (fndecl); /* Reset the source location, we might have been previously deferred, and thus have saved where we were first needed. */ @@ -930,6 +1514,12 @@ synthesize_method (tree fndecl) else finish_mem_initializers (NULL_TREE); } + else if (sfk == sfk_comparison) + { + /* Pass tf_none so the function is just deleted if there's a problem. */ + build_comparison_op (fndecl, tf_none); + need_body = false; + } /* If we haven't yet generated the body of the function, just generate an empty compound statement. */ @@ -941,7 +1531,10 @@ synthesize_method (tree fndecl) } finish_function_body (stmt); - expand_or_defer_fn (finish_function (/*inline_p=*/false)); + finish_function (/*inline_p=*/false); + + if (!DECL_DELETED_FN (fndecl)) + expand_or_defer_fn (fndecl); input_location = save_input_location; @@ -1753,6 +2346,13 @@ get_defaulted_eh_spec (tree decl, tsubst_flags_t complain) if (DECL_CLONED_FUNCTION_P (decl)) decl = DECL_CLONED_FUNCTION (decl); special_function_kind sfk = special_function_p (decl); + if (sfk == sfk_comparison) + { + /* We're in synthesize_method. Start with NULL_TREE, build_comparison_op + will adjust as needed. */ + gcc_assert (decl == current_function_decl); + return NULL_TREE; + } tree ctype = DECL_CONTEXT (decl); tree parms = FUNCTION_FIRST_USER_PARMTYPE (decl); tree parm_type = TREE_VALUE (parms); @@ -1836,7 +2436,14 @@ maybe_explain_implicit_delete (tree decl) informed = true; } } - if (!informed) + if (!informed && sfk == sfk_comparison) + { + inform (DECL_SOURCE_LOCATION (decl), + "%q#D is implicitly deleted because the default " + "definition would be ill-formed:", decl); + build_comparison_op (decl, tf_warning_or_error); + } + else if (!informed) { tree parms = FUNCTION_FIRST_USER_PARMTYPE (decl); bool const_p = false; @@ -1891,10 +2498,18 @@ explain_implicit_non_constexpr (tree decl) bool const_p = CP_TYPE_CONST_P (non_reference (TREE_VALUE (parms))); tree inh = DECL_INHERITED_CTOR (decl); bool dummy; - synthesized_method_walk (DECL_CLASS_CONTEXT (decl), - special_function_p (decl), const_p, - NULL, NULL, NULL, &dummy, true, - &inh, parms); + special_function_kind sfk = special_function_p (decl); + if (sfk == sfk_comparison) + { + DECL_DECLARED_CONSTEXPR_P (decl) = true; + build_comparison_op (decl, tf_warning_or_error); + DECL_DECLARED_CONSTEXPR_P (decl) = false; + } + else + synthesized_method_walk (DECL_CLASS_CONTEXT (decl), + sfk, const_p, + NULL, NULL, NULL, &dummy, true, + &inh, parms); } /* DECL is an instantiation of an inheriting constructor template. Deduce @@ -1933,12 +2548,12 @@ deduce_inheriting_ctor (tree decl) /* Implicitly declare the special function indicated by KIND, as a member of TYPE. For copy constructors and assignment operators, CONST_P indicates whether these functions should take a const - reference argument or a non-const reference. Returns the - FUNCTION_DECL for the implicitly declared function. */ + reference argument or a non-const reference. + Returns the FUNCTION_DECL for the implicitly declared function. */ tree implicitly_declare_fn (special_function_kind kind, tree type, - bool const_p, tree inherited_ctor, + bool const_p, tree pattern_fn, tree inherited_parms) { tree fn; @@ -1950,8 +2565,11 @@ implicitly_declare_fn (special_function_kind kind, tree type, tree this_parm; tree name; HOST_WIDE_INT saved_processing_template_decl; - bool deleted_p; - bool constexpr_p; + bool deleted_p = false; + bool constexpr_p = false; + bool friend_p = (kind == sfk_comparison && DECL_FRIEND_P (pattern_fn)); + tree inherited_ctor = (kind == sfk_inheriting_constructor + ? pattern_fn : NULL_TREE); /* Because we create declarations for implicitly declared functions lazily, we may be creating the declaration for a member of TYPE @@ -1978,6 +2596,7 @@ implicitly_declare_fn (special_function_kind kind, tree type, else return_type = void_type_node; + int this_quals = TYPE_UNQUALIFIED; switch (kind) { case sfk_destructor: @@ -2021,6 +2640,36 @@ implicitly_declare_fn (special_function_kind kind, tree type, } break; } + + case sfk_comparison: + /* If the class definition does not explicitly declare an == operator + function, but declares a defaulted three-way comparison operator + function, an == operator function is declared implicitly with the same + access as the three-way comparison operator function. + + The implicitly-declared == operator for a class X is an inline member + and is defined as defaulted in the definition of X. + + If the three-way comparison operator function is declared as a + non-static const member, the implicitly-declared == operator function + is a member of the form + + bool X::operator==(const X&) const; + + Otherwise, the implicitly-declared == operator function is of the form + + friend bool operator==(const X&, const X&); */ + /* No other comparison operator is implicitly declared. */ + name = ovl_op_identifier (false, EQ_EXPR); + return_type = boolean_type_node; + rhs_parm_type = cp_build_qualified_type (type, TYPE_QUAL_CONST); + rhs_parm_type = cp_build_reference_type (rhs_parm_type, false); + parameter_types = tree_cons (NULL_TREE, rhs_parm_type, parameter_types); + if (friend_p) + parameter_types = tree_cons (NULL_TREE, rhs_parm_type, parameter_types); + this_quals = TYPE_QUAL_CONST; + break; + default: gcc_unreachable (); } @@ -2038,9 +2687,10 @@ implicitly_declare_fn (special_function_kind kind, tree type, else if (cxx_dialect >= cxx11) { raises = noexcept_deferred_spec; - synthesized_method_walk (type, kind, const_p, NULL, &trivial_p, - &deleted_p, &constexpr_p, false, - &inherited_ctor, inherited_parms); + if (kind != sfk_comparison) + synthesized_method_walk (type, kind, const_p, NULL, &trivial_p, + &deleted_p, &constexpr_p, false, + &inherited_ctor, inherited_parms); } else synthesized_method_walk (type, kind, const_p, &raises, &trivial_p, @@ -2062,7 +2712,9 @@ implicitly_declare_fn (special_function_kind kind, tree type, type_set_nontrivial_flag (type, kind); /* Create the function. */ - fn_type = build_method_type_directly (type, return_type, parameter_types); + tree this_type = cp_build_qualified_type (type, this_quals); + fn_type = build_method_type_directly (this_type, return_type, + parameter_types); if (raises) { if (raises != error_mark_node) @@ -2073,16 +2725,25 @@ implicitly_declare_fn (special_function_kind kind, tree type, gcc_assert (seen_error ()); } fn = build_lang_decl (FUNCTION_DECL, name, fn_type); - if (kind != sfk_inheriting_constructor) + if (kind == sfk_comparison) + { + DECL_SOURCE_LOCATION (fn) = DECL_SOURCE_LOCATION (pattern_fn); + DECL_MAYBE_DELETED (fn) = true; + } + else if (kind != sfk_inheriting_constructor) DECL_SOURCE_LOCATION (fn) = DECL_SOURCE_LOCATION (TYPE_NAME (type)); - if (!IDENTIFIER_CDTOR_P (name)) - /* Assignment operator. */ - DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) = OVL_OP_NOP_EXPR; + if (IDENTIFIER_OVL_OP_P (name)) + { + const ovl_op_info_t *op = IDENTIFIER_OVL_OP_INFO (name); + DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) = op->ovl_op_code; + } else if (IDENTIFIER_CTOR_P (name)) DECL_CXX_CONSTRUCTOR_P (fn) = true; - else + else if (IDENTIFIER_DTOR_P (name)) DECL_CXX_DESTRUCTOR_P (fn) = true; + else + gcc_unreachable (); SET_DECL_ALIGN (fn, MINIMUM_METHOD_BOUNDARY); @@ -2097,6 +2758,13 @@ implicitly_declare_fn (special_function_kind kind, tree type, retrofit_lang_decl (decl); DECL_PARM_INDEX (decl) = DECL_PARM_LEVEL (decl) = 1; DECL_ARGUMENTS (fn) = decl; + if (friend_p) + { + /* The second parm of friend op==. */ + tree decl2 = copy_decl (decl); + DECL_CHAIN (decl) = decl2; + DECL_PARM_INDEX (decl2) = 2; + } } else if (kind == sfk_inheriting_constructor) { @@ -2122,7 +2790,7 @@ implicitly_declare_fn (special_function_kind kind, tree type, constexpr_p = DECL_DECLARED_CONSTEXPR_P (inherited_ctor); } /* Add the "this" parameter. */ - this_parm = build_this_parm (fn, fn_type, TYPE_UNQUALIFIED); + this_parm = build_this_parm (fn, fn_type, this_quals); DECL_CHAIN (this_parm) = DECL_ARGUMENTS (fn); DECL_ARGUMENTS (fn) = this_parm; @@ -2141,6 +2809,12 @@ implicitly_declare_fn (special_function_kind kind, tree type, set_linkage_according_to_type (type, fn); if (TREE_PUBLIC (fn)) DECL_COMDAT (fn) = 1; + if (kind == sfk_comparison && !friend_p) + { + /* The implicit op== has the same access as the op<=>. */ + TREE_PRIVATE (fn) = TREE_PRIVATE (pattern_fn); + TREE_PROTECTED (fn) = TREE_PROTECTED (pattern_fn); + } rest_of_decl_compilation (fn, namespace_bindings_p (), at_eof); gcc_assert (!TREE_USED (fn)); @@ -2182,6 +2856,16 @@ defaulted_late_check (tree fn) /* Complain about invalid signature for defaulted fn. */ tree ctx = DECL_CONTEXT (fn); special_function_kind kind = special_function_p (fn); + + if (kind == sfk_comparison) + { + /* If the function was declared constexpr, check that the definition + qualifies. Otherwise we can define the function lazily. */ + if (DECL_DECLARED_CONSTEXPR_P (fn)) + synthesize_method (fn); + return; + } + bool fn_const_p = (copy_fn_p (fn) == 2); tree implicit_fn = implicitly_declare_fn (kind, ctx, fn_const_p, NULL, NULL); @@ -2272,6 +2956,13 @@ defaultable_fn_check (tree fn) else if (move_fn_p (fn)) kind = sfk_move_assignment; } + else if (DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) >= OVL_OP_EQ_EXPR + && DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) <= OVL_OP_SPACESHIP_EXPR) + { + kind = sfk_comparison; + if (!early_check_defaulted_comparison (fn)) + return false; + } if (kind == sfk_none) { @@ -2293,7 +2984,7 @@ defaultable_fn_check (tree fn) if (DECL_NAME (p)) TREE_NO_WARNING (p) = 1; - if (TYPE_BEING_DEFINED (DECL_CONTEXT (fn))) + if (current_class_type && TYPE_BEING_DEFINED (current_class_type)) /* Defer checking. */; else if (!processing_template_decl) defaulted_late_check (fn); diff --git a/gcc/cp/name-lookup.c b/gcc/cp/name-lookup.c index cbb61697d7c..cd0d9551aa3 100644 --- a/gcc/cp/name-lookup.c +++ b/gcc/cp/name-lookup.c @@ -5592,6 +5592,12 @@ get_std_name_hint (const char *name) {"atomic_ref", "", cxx2a}, /* . */ {"bitset", "", cxx11}, + /* */ + {"weak_equality", "", cxx2a}, + {"strong_equality", "", cxx2a}, + {"partial_ordering", "", cxx2a}, + {"weak_ordering", "", cxx2a}, + {"strong_ordering", "", cxx2a}, /* . */ {"complex", "", cxx98}, {"complex_literals", "", cxx14}, diff --git a/gcc/cp/operators.def b/gcc/cp/operators.def index cc52e9bd967..ee0a4c17592 100644 --- a/gcc/cp/operators.def +++ b/gcc/cp/operators.def @@ -104,12 +104,16 @@ DEF_OPERATOR ("|", BIT_IOR_EXPR, "or", OVL_OP_FLAG_BINARY) DEF_OPERATOR ("^", BIT_XOR_EXPR, "eo", OVL_OP_FLAG_BINARY) DEF_OPERATOR ("<<", LSHIFT_EXPR, "ls", OVL_OP_FLAG_BINARY) DEF_OPERATOR (">>", RSHIFT_EXPR, "rs", OVL_OP_FLAG_BINARY) + +/* defaultable_fn_check relies on the ordering of the comparison operators. */ DEF_OPERATOR ("==", EQ_EXPR, "eq", OVL_OP_FLAG_BINARY) DEF_OPERATOR ("!=", NE_EXPR, "ne", OVL_OP_FLAG_BINARY) DEF_OPERATOR ("<", LT_EXPR, "lt", OVL_OP_FLAG_BINARY) DEF_OPERATOR (">", GT_EXPR, "gt", OVL_OP_FLAG_BINARY) DEF_OPERATOR ("<=", LE_EXPR, "le", OVL_OP_FLAG_BINARY) DEF_OPERATOR (">=", GE_EXPR, "ge", OVL_OP_FLAG_BINARY) +DEF_OPERATOR ("<=>", SPACESHIP_EXPR, "ss", OVL_OP_FLAG_BINARY) + DEF_OPERATOR ("&&", TRUTH_ANDIF_EXPR, "aa", OVL_OP_FLAG_BINARY) DEF_OPERATOR ("||", TRUTH_ORIF_EXPR, "oo", OVL_OP_FLAG_BINARY) DEF_OPERATOR (",", COMPOUND_EXPR, "cm", OVL_OP_FLAG_BINARY) diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index 11468c0af42..cbbf946d32c 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -1850,6 +1850,7 @@ enum cp_parser_prec PREC_AND_EXPRESSION, PREC_EQUALITY_EXPRESSION, PREC_RELATIONAL_EXPRESSION, + PREC_SPACESHIP_EXPRESSION, PREC_SHIFT_EXPRESSION, PREC_ADDITIVE_EXPRESSION, PREC_MULTIPLICATIVE_EXPRESSION, @@ -1921,6 +1922,8 @@ static const cp_parser_binary_operations_map_node binops[] = { { CPP_LSHIFT, LSHIFT_EXPR, PREC_SHIFT_EXPRESSION }, { CPP_RSHIFT, RSHIFT_EXPR, PREC_SHIFT_EXPRESSION }, + { CPP_SPACESHIP, SPACESHIP_EXPR, PREC_SPACESHIP_EXPRESSION }, + { CPP_LESS, LT_EXPR, PREC_RELATIONAL_EXPRESSION }, { CPP_GREATER, GT_EXPR, PREC_RELATIONAL_EXPRESSION }, { CPP_LESS_EQ, LE_EXPR, PREC_RELATIONAL_EXPRESSION }, @@ -15507,6 +15510,10 @@ cp_parser_operator (cp_parser* parser, location_t start_loc) op = GE_EXPR; break; + case CPP_SPACESHIP: + op = SPACESHIP_EXPR; + break; + case CPP_AND_AND: op = TRUTH_ANDIF_EXPR; break; diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index b8f8f6dbb59..313b8073a3c 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -19003,6 +19003,7 @@ tsubst_copy_and_build (tree t, case GE_EXPR: case LT_EXPR: case GT_EXPR: + case SPACESHIP_EXPR: case MEMBER_REF: case DOTSTAR_EXPR: { diff --git a/gcc/cp/tree.c b/gcc/cp/tree.c index a4b8b00c922..5cdeb6a07fe 100644 --- a/gcc/cp/tree.c +++ b/gcc/cp/tree.c @@ -5042,6 +5042,9 @@ special_function_p (const_tree decl) return sfk_conversion; if (deduction_guide_p (decl)) return sfk_deduction_guide; + if (DECL_OVERLOADED_OPERATOR_CODE_RAW (decl) >= OVL_OP_EQ_EXPR + && DECL_OVERLOADED_OPERATOR_CODE_RAW (decl) <= OVL_OP_SPACESHIP_EXPR) + return sfk_comparison; return sfk_none; } diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c index 50240537938..38a15d14620 100644 --- a/gcc/cp/typeck.c +++ b/gcc/cp/typeck.c @@ -4889,6 +4889,7 @@ cp_build_binary_op (const op_location_t &location, case EQ_EXPR: case NE_EXPR: + case SPACESHIP_EXPR: if (code0 == VECTOR_TYPE && code1 == VECTOR_TYPE) goto vector_compare; if ((complain & tf_warning) @@ -4965,7 +4966,9 @@ cp_build_binary_op (const op_location_t &location, warn_for_null_address (location, op1, complain); } else if ((code0 == POINTER_TYPE && code1 == POINTER_TYPE) - || (TYPE_PTRDATAMEM_P (type0) && TYPE_PTRDATAMEM_P (type1))) + || (code == SPACESHIP_EXPR + ? TYPE_PTRMEM_P (type0) && TYPE_PTRMEM_P (type1) + : TYPE_PTRDATAMEM_P (type0) && TYPE_PTRDATAMEM_P (type1))) result_type = composite_pointer_type (location, type0, type1, op0, op1, CPO_COMPARISON, complain); @@ -5358,6 +5361,55 @@ cp_build_binary_op (const op_location_t &location, location); } + if (code == SPACESHIP_EXPR) + { + iloc_sentinel s (location); + + tree orig_type0 = TREE_TYPE (orig_op0); + tree_code orig_code0 = TREE_CODE (orig_type0); + tree orig_type1 = TREE_TYPE (orig_op1); + tree_code orig_code1 = TREE_CODE (orig_type1); + if ((orig_code0 == BOOLEAN_TYPE) != (orig_code1 == BOOLEAN_TYPE)) + /* "If one of the operands is of type bool and the other is not, the + program is ill-formed." */ + result_type = NULL_TREE; + else if (code0 == POINTER_TYPE && orig_code0 != POINTER_TYPE + && code1 == POINTER_TYPE && orig_code1 != POINTER_TYPE) + /* We only do array/function-to-pointer conversion if "at least one of + the operands is of pointer type". */ + result_type = NULL_TREE; + else if (orig_code0 == ENUMERAL_TYPE && orig_code1 == ENUMERAL_TYPE + && !(same_type_ignoring_top_level_qualifiers_p + (orig_type0, orig_type1))) + /* "If both operands have arithmetic types, or one operand has integral + type and the other operand has unscoped enumeration type, the usual + arithmetic conversions are applied to the operands." So we don't do + arithmetic conversions if the operands both have enumeral type. */ + result_type = NULL_TREE; + + if (result_type) + build_type = spaceship_type (result_type, complain); + + if (result_type && arithmetic_types_p) + { + /* If a narrowing conversion is required, other than from an integral + type to a floating point type, the program is ill-formed. */ + bool ok = true; + if (TREE_CODE (result_type) == REAL_TYPE + && INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (orig_op0))) + /* OK */; + else if (!check_narrowing (result_type, orig_op0, complain)) + ok = false; + if (TREE_CODE (result_type) == REAL_TYPE + && INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (orig_op1))) + /* OK */; + else if (!check_narrowing (result_type, orig_op1, complain)) + ok = false; + if (!ok && !(complain & tf_error)) + return error_mark_node; + } + } + if (!result_type) { if (complain & tf_error) diff --git a/gcc/testsuite/c-c++-common/cpp/spaceship-1.c b/gcc/testsuite/c-c++-common/cpp/spaceship-1.c new file mode 100644 index 00000000000..a3dc38dd325 --- /dev/null +++ b/gcc/testsuite/c-c++-common/cpp/spaceship-1.c @@ -0,0 +1,6 @@ +/* { dg-do preprocess } */ +/* { dg-options "-std=c11" { target c } } */ + +#define A(x, y) x##y +A(<=, >) /* { dg-error "does not give a valid preprocessing token" "" { target { ! c++2a } } } */ +A(<=>, >) /* { dg-error "does not give a valid preprocessing token" "" { target c++2a } } */ diff --git a/gcc/testsuite/g++.dg/cpp/spaceship-1.C b/gcc/testsuite/g++.dg/cpp/spaceship-1.C new file mode 100644 index 00000000000..241b277c05b --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp/spaceship-1.C @@ -0,0 +1,8 @@ +// { dg-do compile { target c++17_down } } +// { dg-options "-Wno-pointer-arith" } + +struct X {}; +bool operator<= (X, X); +template struct Y {}; +Y<&operator<=> y; +bool foo (bool (*fn) (X, X), int n) { return n+&operator<=> fn; } diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-constexpr1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-constexpr1.C new file mode 100644 index 00000000000..9d008f19fea --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-constexpr1.C @@ -0,0 +1,15 @@ +// { dg-do compile { target c++2a } } + +struct A +{ + int i; + bool operator==(A a) const { return i == a.i; } +}; + +struct B +{ + A a; + bool operator==(const B&) const = default; // { dg-error "A::operator==" } +}; + +constexpr bool x = B() == B(); // { dg-error "non-.constexpr" } diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1.C new file mode 100644 index 00000000000..19a03fb04df --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1.C @@ -0,0 +1,17 @@ +// { dg-do run { target c++2a } } + +struct D +{ + int i; + bool operator==(const D& x) const = default; // OK, returns x.i == y.i + bool operator!=(const D& z) const = default; // OK, returns !(*this == z) +}; + +#define assert(X) do { if (!(X)) __builtin_abort(); } while (0) + +int main() +{ + D d{42}; + assert (d == d); + assert (!(d != d)); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1a.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1a.C new file mode 100644 index 00000000000..7e98c472cd1 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq1a.C @@ -0,0 +1,24 @@ +// { dg-do run { target c++2a } } + +template +struct D +{ + T i; + bool operator==(const D& x) const = default; // OK, returns x.i == y.i + bool operator!=(const D& z) const = default; // OK, returns !(*this == z) +}; + +#define assert(X) do { if (!(X)) __builtin_abort(); } while (0) + +template +void f() +{ + D d{42}; + assert (d == d); + assert (!(d != d)); +} + +int main() +{ + f(); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq2.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq2.C new file mode 100644 index 00000000000..06b988f6e57 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq2.C @@ -0,0 +1,12 @@ +// { dg-do compile { target c++2a } } + +struct D +{ + int i; + bool operator==(const D& x) const = default; // OK, returns x.i == y.i + bool operator!=(const D& z) const = default; // OK, returns !(*this == z) +}; + +constexpr D d{42}; +static_assert (d == d); +static_assert (!(d != d)); diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq3.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq3.C new file mode 100644 index 00000000000..490726de56f --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq3.C @@ -0,0 +1,16 @@ +// { dg-do compile { target c++2a } } + +struct A { + bool operator==(const A&) const; +}; + +struct D +{ + A i; + bool operator==(const D& x) const = default; // { dg-error "A::operator==" } + bool operator!=(const D& z) const = default; // { dg-error "D::operator==" } +}; + +constexpr D d{A()}; +static_assert (d == d); // { dg-error "non-constant|constexpr" } +static_assert (!(d != d)); // { dg-error "non-constant|constexpr" } diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq4.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq4.C new file mode 100644 index 00000000000..d89fc885e50 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq4.C @@ -0,0 +1,8 @@ +// { dg-do compile { target c++2a } } + +struct A { + int operator==(const A&) const = default; // { dg-error "return .bool" } + bool operator==(const A&, const A&) const = default; // { dg-error "exactly one" } + bool operator==(int) const = default; // { dg-error "parameter type" } + bool operator==(const A&) = default; // { dg-error "const" } +}; diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq5.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq5.C new file mode 100644 index 00000000000..ac24f366c94 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq5.C @@ -0,0 +1,10 @@ +// { dg-do compile { target c++2a } } + +struct A { + int &r; // { dg-message "reference" } + bool operator==(const A&) const = default; // { dg-message "deleted" } +}; + +int i; +A a { i }; +bool b = a == a; // { dg-error "deleted" } diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq6.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq6.C new file mode 100644 index 00000000000..f804e133714 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq6.C @@ -0,0 +1,10 @@ +// { dg-do compile { target c++2a } } + +struct A +{ + union { int i; } // { dg-message "union" } + bool operator==(const A&) const = default; // { dg-message "deleted" } +}; + +A a { 42 }; +bool b = a == a; // { dg-error "deleted" } diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-eq7.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq7.C new file mode 100644 index 00000000000..8112eaa4f80 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-eq7.C @@ -0,0 +1,10 @@ +// { dg-do compile { target c++2a } } + +union A +{ + int i; + bool operator==(const A&) const = default; // { dg-message "union" } +}; + +A a { 42 }; +bool b = a == a; // { dg-error "deleted" } diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-err1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-err1.C new file mode 100644 index 00000000000..ce7b56ce574 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-err1.C @@ -0,0 +1,5 @@ +// Test that we suggest adding #include . +// { dg-do compile { target c++2a } } + +auto x = 1<=>2; // { dg-error "" } +// { dg-message "" "" { target *-*-* } .-1 } diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-err2.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-err2.C new file mode 100644 index 00000000000..6461c6ab60a --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-err2.C @@ -0,0 +1,7 @@ +// { dg-do compile { target c++2a } } + +#include +template T())> // { dg-error "31:0 <=> 0" } +void f(T); +//constexpr int f(...) { return 42; } +constexpr int i = f(24); // { dg-error "no match" } diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-rewrite1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-rewrite1.C new file mode 100644 index 00000000000..bb60302622f --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-rewrite1.C @@ -0,0 +1,15 @@ +// This should continue to work. +// { dg-do compile { target c++2a } } + +template +struct A { + template + bool operator==(const A&); +}; + +int main() +{ + A a1; + A a2; + return a1 == a2; +} diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1.C new file mode 100644 index 00000000000..2ca86b748ff --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1.C @@ -0,0 +1,93 @@ +// { dg-do run { target c++2a } } + +#include + +#define assert(X) do { if (!(X)) __builtin_abort(); } while(0) + +void f(){} +void g(){} + +int main() +{ + { + constexpr auto v = 1 <=> 2; + static_assert (__is_same_as (decltype (v), const std::strong_ordering)); + static_assert (!is_eq (v)); + static_assert (is_neq (v)); + static_assert (is_lt (v)); + static_assert (is_lteq (v)); + static_assert (!is_gt (v)); + static_assert (!is_gteq (v)); + } + + { + enum E { a = 0 }; + constexpr auto v = E::a <=> 1; + static_assert (__is_same_as (decltype (v), const std::strong_ordering)); + static_assert (!is_eq (v)); + static_assert (is_neq (v)); + static_assert (is_lt (v)); + static_assert (is_lteq (v)); + static_assert (!is_gt (v)); + static_assert (!is_gteq (v)); + } + + { + enum class E { a, b }; + constexpr auto v = E::a <=> E::b; + static_assert (__is_same_as (decltype (v), const std::strong_ordering)); + static_assert (!is_eq (v)); + static_assert (is_neq (v)); + static_assert (is_lt (v)); + static_assert (is_lteq (v)); + static_assert (!is_gt (v)); + static_assert (!is_gteq (v)); + } + + { + int ar[2]; + constexpr auto v = &ar[1] <=> &ar[0]; + static_assert (__is_same_as (decltype (v), const std::strong_ordering)); + static_assert (!is_eq (v)); + static_assert (is_neq (v)); + static_assert (!is_lt (v)); + static_assert (!is_lteq (v)); + static_assert (is_gt (v)); + static_assert (is_gteq (v)); + } + + { + constexpr auto v = 3.14 <=> 3.14; + static_assert (__is_same_as (decltype (v), const std::partial_ordering)); + static_assert (is_eq (v)); + static_assert (!is_neq (v)); + static_assert (!is_lt (v)); + static_assert (is_lteq (v)); + static_assert (!is_gt (v)); + static_assert (is_gteq (v)); + } + + { + // GCC doesn't consider &f == &g to be a constant expression (PR 69681) + const auto v = &f <=> &g; + static_assert (__is_same_as (decltype (v), const std::strong_equality)); + assert (!is_eq (v)); + assert (is_neq (v)); + } + + { + struct A { int i; int j; }; + constexpr auto v = &A::i <=> &A::j; + static_assert (__is_same_as (decltype (v), const std::strong_equality)); + static_assert (!is_eq (v)); + static_assert (is_neq (v)); + } + + { + struct A { void f(); }; + constexpr auto v = &A::f <=> &A::f; + static_assert (__is_same_as (decltype (v), const std::strong_equality)); + static_assert (is_eq (v)); + static_assert (!is_neq (v)); + } +} diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1a.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1a.C new file mode 100644 index 00000000000..1dc95497fd9 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar1a.C @@ -0,0 +1,41 @@ +// { dg-do run { target c++2a } } + +#include + +#define assert(X) do { if (!(X)) __builtin_abort(); } while(0) + +void f(){} +void g(){} + +template +constexpr bool check(T a, U b, R expected) +{ + auto r = a <=> b; + static_assert (__is_same_as (decltype (r), R)); + return r == expected; +} + +int main() +{ + static_assert (check (1, 2, std::strong_ordering::less)); + + enum E1 { a = 0 }; + static_assert (check (a, 1, std::strong_ordering::less)); + + enum class E2 { a, b }; + static_assert (check (E2::a, E2::b, std::strong_ordering::less)); + + int ar[2]; + static_assert (check (&ar[1], &ar[0], std::strong_ordering::greater)); + + static_assert (check (3.14, 3.14, std::partial_ordering::equivalent)); + + // GCC doesn't consider &f == &g to be a constant expression (PR 69681) + assert (check (&f, &g, std::strong_equality::nonequal)); + + struct A { int i; int j; }; + static_assert (check (&A::i, &A::j, std::strong_equality::nonequal)); + + struct A2 { void f(); }; + static_assert (check (&A2::f, &A2::f, std::strong_equality::equal)); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar2.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar2.C new file mode 100644 index 00000000000..d3cb0a6b0f0 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar2.C @@ -0,0 +1,11 @@ +// { dg-do compile { target c++2a } } + +#include + +int main() +{ + { true <=> 1; } // { dg-error "bool" } + { int a[2]; a <=> a; } // { dg-error "2" } + { -1 <=> 1U; } // { dg-error "narrowing" } + { enum A { a }; enum B { b }; a <=> b; } // { dg-error "A" } +} diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar3.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar3.C new file mode 100644 index 00000000000..20bc8e61455 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-scalar3.C @@ -0,0 +1,21 @@ +// { dg-do run { target c++2a } } +// { dg-options "-fext-numeric-literals" } + +#include + +int main() +{ + // GCC complex literal extension + { + constexpr auto v = 1 <=> 1i; + static_assert (__is_same_as (decltype (v), const std::strong_equality)); + static_assert (!is_eq (v)); + static_assert (is_neq (v)); + } + { + constexpr auto v = 1i <=> 1.0i; + static_assert (__is_same_as (decltype (v), const std::weak_equality)); + static_assert (is_eq (v)); + static_assert (!is_neq (v)); + } +} diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-sfinae1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-sfinae1.C new file mode 100644 index 00000000000..6a03f54b91a --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-sfinae1.C @@ -0,0 +1,7 @@ +// { dg-do compile { target c++2a } } + +// missing #include +template T()) == 0> +void f(T); +constexpr int f(...) { return 42; } +constexpr int i = f(24); diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1.C new file mode 100644 index 00000000000..2a35de99e09 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1.C @@ -0,0 +1,43 @@ +// Test with all operators explicitly defaulted. +// { dg-do run { target c++2a } } + +#include + +struct D +{ + int i; + auto operator<=>(const D& x) const = default; + bool operator==(const D& x) const = default; + bool operator!=(const D& x) const = default; + bool operator<(const D& x) const = default; + bool operator<=(const D& x) const = default; + bool operator>(const D& x) const = default; + bool operator>=(const D& x) const = default; +}; + +#define assert(X) do { if (!(X)) __builtin_abort(); } while (0) + +int main() +{ + D d{42}; + D d2{24}; + + assert (is_eq (d <=> d)); + assert (is_lteq (d <=> d)); + assert (is_gteq (d <=> d)); + assert (is_lt (d2 <=> d)); + assert (is_lteq (d2 <=> d)); + assert (is_gt (d <=> d2)); + assert (is_gteq (d <=> d2)); + + assert (d == d); + assert (!(d2 == d)); + assert (!(d == d2)); + assert (d != d2); + assert (!(d2 != d2)); + + assert (d2 < d); + assert (d2 <= d); + assert (d > d2); + assert (d >= d2); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1a.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1a.C new file mode 100644 index 00000000000..32314579dcb --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth1a.C @@ -0,0 +1,113 @@ +// Test with all operators explicitly defaulted. +// { dg-do run { target c++2a } } + +#include + +template +struct D +{ + T i; + auto operator<=>(const D& x) const = default; + bool operator==(const D& x) const = default; + bool operator!=(const D& x) const = default; + bool operator<(const D& x) const = default; + bool operator<=(const D& x) const = default; + bool operator>(const D& x) const = default; + bool operator>=(const D& x) const = default; +}; + +template +struct E +{ + T i; + auto operator<=>(const E& x) const = default; + // auto operator==(const E& x) const = default; + // auto operator!=(const E& x) const = default; + // auto operator<(const E& x) const = default; + // auto operator<=(const E& x) const = default; + // auto operator>(const E& x) const = default; + // auto operator>=(const E& x) const = default; +}; + +template +struct F +{ + T i; + constexpr auto operator<=>(T x) const { return i<=>x; } + constexpr bool operator== (T x) const { return i==x; } +}; + +#define assert(X) do { if (!(X)) __builtin_abort(); } while (0) + +template +constexpr bool check_eq (T d, U d2) +{ + return is_eq (d <=> d2) + && is_eq (d2 <=> d) + && is_lteq (d <=> d2) + && is_lteq (d2 <=> d) + && !is_lt (d <=> d2) + && !is_lt (d2 <=> d) + && is_gteq (d <=> d2) + && is_gteq (d2 <=> d) + && !is_gt (d <=> d2) + && !is_gt (d2 <=> d) + && d == d2 + && d2 == d + && !(d != d2) + && !(d2 != d) + && d >= d2 + && d <= d2 + && d2 >= d + && d2 <= d + && !(d < d2) + && !(d2 < d) + && !(d > d2) + && !(d2 > d); +} + +template +constexpr bool check_less (T d, U d2) +{ + return !is_eq (d <=> d2) + && !is_eq (d2 <=> d) + && is_lteq (d <=> d2) + && !is_lteq (d2 <=> d) + && is_lt (d <=> d2) + && !is_lt (d2 <=> d) + && !is_gteq (d <=> d2) + && is_gteq (d2 <=> d) + && !is_gt (d <=> d2) + && is_gt (d2 <=> d) + && !(d == d2) + && !(d2 == d) + && (d != d2) + && (d2 != d) + && !(d >= d2) + && (d <= d2) + && (d2 >= d) + && !(d2 <= d) + && (d < d2) + && !(d2 < d) + && !(d > d2) + && (d2 > d); +} + +int main() +{ + constexpr D d{42}; + constexpr D d2{24}; + + static_assert (check_eq (d, d)); + static_assert (check_less (d2, d)); + + constexpr E e { 3.14 }; + constexpr E ee { 2.72 }; + static_assert (check_eq (e, e)); + static_assert (check_less (ee, e)); + + constexpr F f { 'b' }; + static_assert (check_eq (f, 'b')); + static_assert (check_less (f, 'c')); + static_assert (check_less ('a', f)); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth2.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth2.C new file mode 100644 index 00000000000..cf23c9771e0 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth2.C @@ -0,0 +1,43 @@ +// Test with only spaceship defaulted. +// { dg-do run { target c++2a } } + +#include + +struct D +{ + int i; + auto operator<=>(const D& x) const = default; + // auto operator==(const D& x) const = default; + // auto operator!=(const D& x) const = default; + // auto operator<(const D& x) const = default; + // auto operator<=(const D& x) const = default; + // auto operator>(const D& x) const = default; + // auto operator>=(const D& x) const = default; +}; + +#define assert(X) do { if (!(X)) __builtin_abort(); } while (0) + +int main() +{ + D d{42}; + D d2{24}; + + assert (is_eq (d <=> d)); + assert (is_lteq (d <=> d)); + assert (is_gteq (d <=> d)); + assert (is_lt (d2 <=> d)); + assert (is_lteq (d2 <=> d)); + assert (is_gt (d <=> d2)); + assert (is_gteq (d <=> d2)); + + assert (d == d); + assert (!(d2 == d)); + assert (!(d == d2)); + assert (d != d2); + assert (!(d2 != d2)); + + assert (d2 < d); + assert (d2 <= d); + assert (d > d2); + assert (d >= d2); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3.C new file mode 100644 index 00000000000..0fc5aa2c9b8 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3.C @@ -0,0 +1,48 @@ +// Test for reversed candidates. +// { dg-do run { target c++2a } } + +#include + +struct D +{ + int i; + auto operator<=>(int x) const { return i<=>x; } + bool operator== (int x) const { return i==x; } +}; + +#define assert(X) do { if (!(X)) __builtin_abort(); } while (0) + +int main() +{ + D d{42}; + int d1 = 42; + int d2 = 24; + + assert (is_eq (d <=> d1)); + assert (is_eq (d1 <=> d)); + assert (is_lteq (d <=> d1)); + assert (is_lteq (d1 <=> d)); + assert (is_gteq (d <=> d1)); + assert (is_gteq (d1 <=> d)); + assert (is_lt (d2 <=> d)); + assert (is_lteq (d2 <=> d)); + assert (is_gt (d <=> d2)); + assert (is_gteq (d <=> d2)); + + assert (d == d1); + assert (d1 == d); + assert (!(d2 == d)); + assert (!(d == d2)); + assert (d != d2); + assert (d2 != d); + assert (!(d != d1)); + assert (!(d1 != d)); + + assert (d2 < d); + assert (d2 <= d); + assert (d1 <= d); + assert (d > d2); + assert (d >= d2); + assert (d >= d1); + assert (d <= d1); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3a.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3a.C new file mode 100644 index 00000000000..89f84899fcc --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-synth3a.C @@ -0,0 +1,54 @@ +// Test for reversed candidates. +// { dg-do run { target c++2a } } + +#include + +struct D +{ + int i; + auto operator<=>(int x) const { return i<=>x; } + bool operator== (int x) const { return i==x; } +}; + +#define assert(X) do { if (!(X)) __builtin_abort(); } while (0) + +template +void f() +{ + D d{42}; + int d1 = 42; + int d2 = 24; + + assert (is_eq (d <=> d1)); + assert (is_eq (d1 <=> d)); + assert (is_lteq (d <=> d1)); + assert (is_lteq (d1 <=> d)); + assert (is_gteq (d <=> d1)); + assert (is_gteq (d1 <=> d)); + assert (is_lt (d2 <=> d)); + assert (is_lteq (d2 <=> d)); + assert (is_gt (d <=> d2)); + assert (is_gteq (d <=> d2)); + + assert (d == d1); + assert (d1 == d); + assert (!(d2 == d)); + assert (!(d == d2)); + assert (d != d2); + assert (d2 != d); + assert (!(d != d1)); + assert (!(d1 != d)); + + assert (d2 < d); + assert (d2 <= d); + assert (d1 <= d); + assert (d > d2); + assert (d >= d2); + assert (d >= d1); + assert (d <= d1); +} + +int main() +{ + f(); +} diff --git a/gcc/testsuite/g++.dg/cpp2a/spaceship-weak1.C b/gcc/testsuite/g++.dg/cpp2a/spaceship-weak1.C new file mode 100644 index 00000000000..1ff39549973 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/spaceship-weak1.C @@ -0,0 +1,15 @@ +// Test explicit weak_ordering. +// { dg-do compile { target c++2a } } + +#include +struct A +{ + int i; + std::weak_ordering operator<=> (const A&) const = default; +}; + +constexpr A a = { 42 }; +constexpr auto c = a <=> a; +static_assert (std::same_as ); +static_assert (std::is_eq (c)); + diff --git a/gcc/testsuite/g++.dg/lookup/pr21802.C b/gcc/testsuite/g++.dg/lookup/pr21802.C index 139f7b49c25..18b2219166a 100644 --- a/gcc/testsuite/g++.dg/lookup/pr21802.C +++ b/gcc/testsuite/g++.dg/lookup/pr21802.C @@ -60,6 +60,7 @@ struct Y : virtual X template int operator&(T x) { return m + x + 1; } friend int operator==(Y o, int x) { return o.m + x + 1; } + int operator!=(int x) { return m + x + 1; } }; /* The folloiwng "FooN" functions each contain a different way to call and to @@ -81,7 +82,6 @@ Foo1 (T) { int t = x | I; assert (t == 7); } { int t = x && I; assert (t == 7); } { int t = x || I; assert (t == 7); } - { int t = x != I; assert (t == 7); } { int t = x < I; assert (t == 7); } { int t = x <= I; assert (t == 7); } { int t = x > I; assert (t == 7); } @@ -104,6 +104,7 @@ Foo1 (T) { int t = x & I; assert (t == 8); } { int t = &x; assert (t == 8); } { int t = x == I; assert (t == 8); } + { int t = x != I; assert (t == 8); } } template @@ -204,7 +205,6 @@ Foo4 (T) { int t = x.operator| (I); assert (t == 7); } { int t = x.operator&& (I); assert (t == 7); } { int t = x.operator|| (I); assert (t == 7); } - { int t = x.operator!= (I); assert (t == 7); } { int t = x.operator< (I); assert (t == 7); } { int t = x.operator<= (I); assert (t == 7); } { int t = x.operator> (I); assert (t == 7); } @@ -227,6 +227,7 @@ Foo4 (T) { int t = x.operator& (); assert (t == 8); } { int t = x.operator& (I); assert (t == 8); } { int t = operator== (x, I); assert (t == 8); } + { int t = x.operator!= (I); assert (t == 8); } } diff --git a/gcc/testsuite/g++.old-deja/g++.robertl/eb22.C b/gcc/testsuite/g++.old-deja/g++.robertl/eb22.C index 62dd18d0ccb..c0921bb43de 100644 --- a/gcc/testsuite/g++.old-deja/g++.robertl/eb22.C +++ b/gcc/testsuite/g++.old-deja/g++.robertl/eb22.C @@ -11,18 +11,17 @@ public: operator int() const {return 2;} }; -bool operator==(const MyInt& a, const int& b) // { dg-message "operator==" } candidate +bool operator==(const MyInt& a, const int& b) // { dg-message "operator==" "" { target c++17_down } } { return (int)a == b; } -bool operator==(const MyInt& a, const MyInt& b) // { dg-message "operator==" } candidate +bool operator==(const MyInt& a, const MyInt& b) // { dg-message "operator==" "" { target c++17_down } } { return (int)a == (int)b; } bool f() { - return 3 == MyInt(); // { dg-error "ambiguous" "err" } - // { dg-message "operator==" "match candidate text" { target *-*-* } .-1 } + return 3 == MyInt(); // { dg-error "ambiguous" "err" { target c++17_down } } } diff --git a/libcpp/ChangeLog b/libcpp/ChangeLog index a13f7508599..8be84386d6c 100644 --- a/libcpp/ChangeLog +++ b/libcpp/ChangeLog @@ -1,3 +1,8 @@ +2019-11-05 Tim van Deurzen + + * cpplib.h: Add spaceship operator for C++. + * lex.c: Implement conditional lexing of spaceship operator for C++20. + 2019-10-31 Jakub Jelinek PR preprocessor/92296 diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h index c655d3ffc90..ed108f17bfa 100644 --- a/libcpp/include/cpplib.h +++ b/libcpp/include/cpplib.h @@ -78,6 +78,7 @@ struct _cpp_file; OP(NOT_EQ, "!=") \ OP(GREATER_EQ, ">=") \ OP(LESS_EQ, "<=") \ + OP(SPACESHIP, "<=>") \ \ /* These two are unary + / - in preprocessor expressions. */ \ OP(PLUS_EQ, "+=") /* math */ \ diff --git a/libcpp/lex.c b/libcpp/lex.c index 3e7d1c37ff5..e95eda3f44e 100644 --- a/libcpp/lex.c +++ b/libcpp/lex.c @@ -2980,7 +2980,13 @@ _cpp_lex_direct (cpp_reader *pfile) result->type = CPP_LESS; if (*buffer->cur == '=') - buffer->cur++, result->type = CPP_LESS_EQ; + { + buffer->cur++, result->type = CPP_LESS_EQ; + if (*buffer->cur == '>' + && CPP_OPTION (pfile, cplusplus) + && CPP_OPTION (pfile, lang) >= CLK_GNUCXX2A) + buffer->cur++, result->type = CPP_SPACESHIP; + } else if (*buffer->cur == '<') { buffer->cur++; @@ -3491,6 +3497,7 @@ cpp_avoid_paste (cpp_reader *pfile, const cpp_token *token1, || (CPP_OPTION (pfile, objc) && token1->val.str.text[0] == '@' && (b == CPP_NAME || b == CPP_STRING))); + case CPP_LESS_EQ: return c == '>'; case CPP_STRING: case CPP_WSTRING: case CPP_UTF8STRING: diff --git a/libstdc++-v3/ChangeLog b/libstdc++-v3/ChangeLog index 05f6a245335..4f05f774b8b 100644 --- a/libstdc++-v3/ChangeLog +++ b/libstdc++-v3/ChangeLog @@ -1,5 +1,10 @@ 2019-11-05 Jonathan Wakely + * libsupc++/compare: New header. + * libsupc++/Makefile.am (std_HEADERS): Add compare. + * include/std/version: Define __cpp_lib_three_way_comparison. + * include/std/functional: #include . + * include/std/version [!_GLIBCXX_HOSTED]: Do not define feature test macros for features that are only present in hosted builds. diff --git a/libstdc++-v3/include/Makefile.am b/libstdc++-v3/include/Makefile.am index 3e526dc14b7..49fd41360bc 100644 --- a/libstdc++-v3/include/Makefile.am +++ b/libstdc++-v3/include/Makefile.am @@ -1385,8 +1385,9 @@ endif # , , , , , , # , , , , , # and any files which they include (and which we provide). -# , , , and are installed by -# libsupc++, so only the others and the sub-includes are copied here. +# , , , and +# are installed by libsupc++, so only the others and the sub-includes +# are copied here. install-freestanding-headers: $(mkinstalldirs) $(DESTDIR)${gxx_include_dir}/bits for file in c++0x_warning.h atomic_base.h concept_check.h move.h; do \ diff --git a/libstdc++-v3/include/std/functional b/libstdc++-v3/include/std/functional index dad7781378b..b6a57ac9b54 100644 --- a/libstdc++-v3/include/std/functional +++ b/libstdc++-v3/include/std/functional @@ -66,6 +66,7 @@ #endif #if __cplusplus > 201703L # include +# include #endif namespace std _GLIBCXX_VISIBILITY(default) diff --git a/libstdc++-v3/include/std/version b/libstdc++-v3/include/std/version index 2df13696668..fa6d27467f7 100644 --- a/libstdc++-v3/include/std/version +++ b/libstdc++-v3/include/std/version @@ -187,6 +187,9 @@ #define __cpp_lib_list_remove_return_type 201806L #define __cpp_lib_math_constants 201907L #define __cpp_lib_span 201902L +#if __cpp_impl_three_way_comparison >= 201907L +# define __cpp_lib_three_way_comparison 201711L +#endif #define __cpp_lib_to_array 201907L #endif #endif // C++2a diff --git a/libstdc++-v3/libsupc++/Makefile.am b/libstdc++-v3/libsupc++/Makefile.am index eec7b953514..8303f88ef41 100644 --- a/libstdc++-v3/libsupc++/Makefile.am +++ b/libstdc++-v3/libsupc++/Makefile.am @@ -31,7 +31,7 @@ toolexeclib_LTLIBRARIES = libsupc++.la noinst_LTLIBRARIES = libsupc++convenience.la std_HEADERS = \ - cxxabi.h exception initializer_list new typeinfo + compare cxxabi.h exception initializer_list new typeinfo bits_HEADERS = \ atomic_lockfree_defines.h cxxabi_forced.h \ diff --git a/libstdc++-v3/libsupc++/compare b/libstdc++-v3/libsupc++/compare new file mode 100644 index 00000000000..379b2d48582 --- /dev/null +++ b/libstdc++-v3/libsupc++/compare @@ -0,0 +1,644 @@ +// -*- C++ -*- operator<=> three-way comparison support. + +// Copyright (C) 2019 Free Software Foundation, Inc. +// +// This file is part of GCC. +// +// GCC is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3, or (at your option) +// any later version. +// +// GCC is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// . + +/** @file compare + * This is a Standard C++ Library header. + */ + +#ifndef _COMPARE +#define _COMPARE + +#pragma GCC system_header + +#if __cplusplus > 201703L && __cpp_impl_three_way_comparison >= 201907L + +#pragma GCC visibility push(default) + +#include + +namespace std +{ +#define __cpp_lib_three_way_comparison 201711L + + // [cmp.categories], comparison category types + + namespace __cmp_cat + { + enum class _Eq + { equal = 0, equivalent = equal, nonequal = 1, nonequivalent = nonequal }; + + enum class _Ord { _Less = -1, _Greater = 1 }; + + enum class _Ncmp { _Unordered = -127 }; + + struct __unspec + { + constexpr __unspec(__unspec*) { } + }; + } + + class weak_equality + { + int _M_value; + + constexpr explicit + weak_equality(__cmp_cat::_Eq __val) noexcept + : _M_value(int(__val)) + { } + + public: + // valid values + + static const weak_equality equivalent; + static const weak_equality nonequivalent; + + // comparisons + + friend constexpr bool + operator==(weak_equality __v, __cmp_cat::__unspec) noexcept + { return __v._M_value == 0; } + + friend constexpr bool + operator==(weak_equality, weak_equality) noexcept = default; + + friend constexpr weak_equality + operator<=>(weak_equality __v, __cmp_cat::__unspec) noexcept + { return __v; } + + friend constexpr weak_equality + operator<=>(__cmp_cat::__unspec, weak_equality __v) noexcept + { return __v; } + }; + + // valid values' definitions + inline constexpr weak_equality + weak_equality::equivalent(__cmp_cat::_Eq::equivalent); + + inline constexpr weak_equality + weak_equality::nonequivalent(__cmp_cat::_Eq::nonequivalent); + + class strong_equality + { + int _M_value; + + constexpr explicit + strong_equality(__cmp_cat::_Eq __val) noexcept + : _M_value(int(__val)) + { } + + public: + // valid values + + static const strong_equality equal; + static const strong_equality nonequal; + static const strong_equality equivalent; + static const strong_equality nonequivalent; + + // conversion + constexpr operator weak_equality() const noexcept + { + if (_M_value == 0) + return weak_equality::equivalent; + else + return weak_equality::nonequivalent; + } + + // comparisons + + friend constexpr bool + operator==(strong_equality __v, __cmp_cat::__unspec) noexcept + { return __v._M_value == 0; } + + friend constexpr bool + operator==(strong_equality, strong_equality) noexcept = default; + + friend constexpr strong_equality + operator<=>(strong_equality __v, __cmp_cat::__unspec) noexcept + { return __v; } + + friend constexpr strong_equality + operator<=>(__cmp_cat::__unspec, strong_equality __v) noexcept + { return __v; } + }; + + // valid values' definitions + inline constexpr strong_equality + strong_equality::equal(__cmp_cat::_Eq::equal); + + inline constexpr strong_equality + strong_equality::nonequal(__cmp_cat::_Eq::nonequal); + + inline constexpr strong_equality + strong_equality::equivalent(__cmp_cat::_Eq::equivalent); + + inline constexpr strong_equality + strong_equality::nonequivalent(__cmp_cat::_Eq::nonequivalent); + + class partial_ordering + { + int _M_value; + bool _M_is_ordered; + + constexpr explicit + partial_ordering(__cmp_cat::_Eq __v) noexcept + : _M_value(int(__v)), _M_is_ordered(true) + { } + + constexpr explicit + partial_ordering(__cmp_cat::_Ord __v) noexcept + : _M_value(int(__v)), _M_is_ordered(true) + { } + + constexpr explicit + partial_ordering(__cmp_cat::_Ncmp __v) noexcept + : _M_value(int(__v)), _M_is_ordered(false) + { } + + public: + // valid values + static const partial_ordering less; + static const partial_ordering equivalent; + static const partial_ordering greater; + static const partial_ordering unordered; + + // conversion + constexpr operator weak_equality() const noexcept + { + if (_M_value == 0) + return weak_equality::equivalent; + else + return weak_equality::nonequivalent; + } + + // comparisons + friend constexpr bool + operator==(partial_ordering __v, __cmp_cat::__unspec) noexcept + { return __v._M_is_ordered && __v._M_value == 0; } + + friend constexpr bool + operator==(partial_ordering, partial_ordering) noexcept = default; + + friend constexpr bool + operator< (partial_ordering __v, __cmp_cat::__unspec) noexcept + { return __v._M_is_ordered && __v._M_value < 0; } + + friend constexpr bool + operator> (partial_ordering __v, __cmp_cat::__unspec) noexcept + { return __v._M_is_ordered && __v._M_value > 0; } + + friend constexpr bool + operator<=(partial_ordering __v, __cmp_cat::__unspec) noexcept + { return __v._M_is_ordered && __v._M_value <= 0; } + + friend constexpr bool + operator>=(partial_ordering __v, __cmp_cat::__unspec) noexcept + { return __v._M_is_ordered && __v._M_value >= 0; } + + friend constexpr bool + operator< (__cmp_cat::__unspec, partial_ordering __v) noexcept + { return __v._M_is_ordered && 0 < __v._M_value; } + + friend constexpr bool + operator> (__cmp_cat::__unspec, partial_ordering __v) noexcept + { return __v._M_is_ordered && 0 > __v._M_value; } + + friend constexpr bool + operator<=(__cmp_cat::__unspec, partial_ordering __v) noexcept + { return __v._M_is_ordered && 0 <= __v._M_value; } + + friend constexpr bool + operator>=(__cmp_cat::__unspec, partial_ordering __v) noexcept + { return __v._M_is_ordered && 0 >= __v._M_value; } + + friend constexpr partial_ordering + operator<=>(partial_ordering __v, __cmp_cat::__unspec) noexcept + { return __v; } + + friend constexpr partial_ordering + operator<=>(__cmp_cat::__unspec, partial_ordering __v) noexcept + { + if (__v < 0) + return partial_ordering::greater; + else if (__v > 0) + return partial_ordering::less; + else + return __v; + } + }; + + // valid values' definitions + inline constexpr partial_ordering + partial_ordering::less(__cmp_cat::_Ord::_Less); + + inline constexpr partial_ordering + partial_ordering::equivalent(__cmp_cat::_Eq::equivalent); + + inline constexpr partial_ordering + partial_ordering::greater(__cmp_cat::_Ord::_Greater); + + inline constexpr partial_ordering + partial_ordering::unordered(__cmp_cat::_Ncmp::_Unordered); + + class weak_ordering + { + int _M_value; + + constexpr explicit + weak_ordering(__cmp_cat::_Eq __v) noexcept : _M_value(int(__v)) + { } + + constexpr explicit + weak_ordering(__cmp_cat::_Ord __v) noexcept : _M_value(int(__v)) + { } + + public: + // valid values + static const weak_ordering less; + static const weak_ordering equivalent; + static const weak_ordering greater; + + // conversions + constexpr operator weak_equality() const noexcept + { + if (_M_value == 0) + return weak_equality::equivalent; + else + return weak_equality::nonequivalent; + } + + constexpr operator partial_ordering() const noexcept + { + if (_M_value == 0) + return partial_ordering::equivalent; + else if (_M_value < 0) + return partial_ordering::less; + else + return partial_ordering::greater; + } + + // comparisons + friend constexpr bool + operator==(weak_ordering __v, __cmp_cat::__unspec) noexcept + { return __v._M_value == 0; } + + friend constexpr bool + operator==(weak_ordering, weak_ordering) noexcept = default; + + friend constexpr bool + operator< (weak_ordering __v, __cmp_cat::__unspec) noexcept + { return __v._M_value < 0; } + + friend constexpr bool + operator> (weak_ordering __v, __cmp_cat::__unspec) noexcept + { return __v._M_value > 0; } + + friend constexpr bool + operator<=(weak_ordering __v, __cmp_cat::__unspec) noexcept + { return __v._M_value <= 0; } + + friend constexpr bool + operator>=(weak_ordering __v, __cmp_cat::__unspec) noexcept + { return __v._M_value >= 0; } + + friend constexpr bool + operator< (__cmp_cat::__unspec, weak_ordering __v) noexcept + { return 0 < __v._M_value; } + + friend constexpr bool + operator> (__cmp_cat::__unspec, weak_ordering __v) noexcept + { return 0 > __v._M_value; } + + friend constexpr bool + operator<=(__cmp_cat::__unspec, weak_ordering __v) noexcept + { return 0 <= __v._M_value; } + + friend constexpr bool + operator>=(__cmp_cat::__unspec, weak_ordering __v) noexcept + { return 0 >= __v._M_value; } + + friend constexpr weak_ordering + operator<=>(weak_ordering __v, __cmp_cat::__unspec) noexcept + { return __v; } + + friend constexpr weak_ordering + operator<=>(__cmp_cat::__unspec, weak_ordering __v) noexcept + { + if (__v < 0) + return weak_ordering::greater; + else if (__v > 0) + return weak_ordering::less; + else + return __v; + } + }; + + // valid values' definitions + inline constexpr weak_ordering + weak_ordering::less(__cmp_cat::_Ord::_Less); + + inline constexpr weak_ordering + weak_ordering::equivalent(__cmp_cat::_Eq::equivalent); + + inline constexpr weak_ordering + weak_ordering::greater(__cmp_cat::_Ord::_Greater); + + class strong_ordering + { + int _M_value; + + constexpr explicit + strong_ordering(__cmp_cat::_Eq __v) noexcept + : _M_value(int(__v)) + { } + + constexpr explicit + strong_ordering(__cmp_cat::_Ord __v) noexcept + : _M_value(int(__v)) + { } + + public: + // valid values + static const strong_ordering less; + static const strong_ordering equal; + static const strong_ordering equivalent; + static const strong_ordering greater; + + // conversions + constexpr operator weak_equality() const noexcept + { + if (_M_value == 0) + return weak_equality::equivalent; + else + return weak_equality::nonequivalent; + } + + constexpr operator strong_equality() const noexcept + { + if (_M_value == 0) + return strong_equality::equal; + else + return strong_equality::nonequal; + } + + constexpr operator partial_ordering() const noexcept + { + if (_M_value == 0) + return partial_ordering::equivalent; + else if (_M_value < 0) + return partial_ordering::less; + else + return partial_ordering::greater; + } + + constexpr operator weak_ordering() const noexcept + { + if (_M_value == 0) + return weak_ordering::equivalent; + else if (_M_value < 0) + return weak_ordering::less; + else + return weak_ordering::greater; + } + + // comparisons + friend constexpr bool + operator==(strong_ordering __v, __cmp_cat::__unspec) noexcept + { return __v._M_value == 0; } + + friend constexpr bool + operator==(strong_ordering, strong_ordering) noexcept = default; + + friend constexpr bool + operator< (strong_ordering __v, __cmp_cat::__unspec) noexcept + { return __v._M_value < 0; } + + friend constexpr bool + operator> (strong_ordering __v, __cmp_cat::__unspec) noexcept + { return __v._M_value > 0; } + + friend constexpr bool + operator<=(strong_ordering __v, __cmp_cat::__unspec) noexcept + { return __v._M_value <= 0; } + + friend constexpr bool + operator>=(strong_ordering __v, __cmp_cat::__unspec) noexcept + { return __v._M_value >= 0; } + + friend constexpr bool + operator< (__cmp_cat::__unspec, strong_ordering __v) noexcept + { return 0 < __v._M_value; } + + friend constexpr bool + operator> (__cmp_cat::__unspec, strong_ordering __v) noexcept + { return 0 > __v._M_value; } + + friend constexpr bool + operator<=(__cmp_cat::__unspec, strong_ordering __v) noexcept + { return 0 <= __v._M_value; } + + friend constexpr bool + operator>=(__cmp_cat::__unspec, strong_ordering __v) noexcept + { return 0 >= __v._M_value; } + + friend constexpr strong_ordering + operator<=>(strong_ordering __v, __cmp_cat::__unspec) noexcept + { return __v; } + + friend constexpr strong_ordering + operator<=>(__cmp_cat::__unspec, strong_ordering __v) noexcept + { + if (__v < 0) + return strong_ordering::greater; + else if (__v > 0) + return strong_ordering::less; + else + return __v; + } + }; + + // valid values' definitions + inline constexpr strong_ordering + strong_ordering::less(__cmp_cat::_Ord::_Less); + + inline constexpr strong_ordering + strong_ordering::equal(__cmp_cat::_Eq::equal); + + inline constexpr strong_ordering + strong_ordering::equivalent(__cmp_cat::_Eq::equivalent); + + inline constexpr strong_ordering + strong_ordering::greater(__cmp_cat::_Ord::_Greater); + + + // named comparison functions + constexpr bool + is_eq(weak_equality __cmp) noexcept + { return __cmp == 0; } + + constexpr bool + is_neq(weak_equality __cmp) noexcept + { return __cmp != 0; } + + constexpr bool + is_lt (partial_ordering __cmp) noexcept + { return __cmp < 0; } + + constexpr bool + is_lteq(partial_ordering __cmp) noexcept + { return __cmp <= 0; } + + constexpr bool + is_gt (partial_ordering __cmp) noexcept + { return __cmp > 0; } + + constexpr bool + is_gteq(partial_ordering __cmp) noexcept + { return __cmp >= 0; } + + // [cmp.common], common comparison category type + template + struct common_comparison_category { + // using type = TODO + }; + + template + using common_comparison_category_t + = typename common_comparison_category<_Ts...>::type; + +#if __cpp_concepts + namespace __detail + { + template + concept __compares_as + = same_as, _Cat>; + + template + concept __partially_ordered_with + = requires(const remove_reference_t<_Tp>& __t, + const remove_reference_t<_Up>& __u) { + { __t < __u } -> boolean; + { __t > __u } -> boolean; + { __t <= __u } -> boolean; + { __t >= __u } -> boolean; + { __u < __t } -> boolean; + { __u > __t } -> boolean; + { __u <= __t } -> boolean; + { __u >= __t } -> boolean; + }; + } // namespace __detail + + // [cmp.concept], concept three_way_comparable + template + concept three_way_comparable + = __detail::__weakly_eq_cmp_with<_Tp, _Tp> + && (!convertible_to<_Cat, partial_ordering> + || __detail::__partially_ordered_with<_Tp, _Tp>) + && requires(const remove_reference_t<_Tp>& __a, + const remove_reference_t<_Tp>& __b) { + { __a <=> __b } -> __detail::__compares_as<_Cat>; + }; + + template + concept three_way_comparable_with + = __detail::__weakly_eq_cmp_with<_Tp, _Up> + && (!convertible_to<_Cat, partial_ordering> + || __detail::__partially_ordered_with<_Tp, _Up>) + && three_way_comparable<_Tp, _Cat> + && three_way_comparable<_Up, _Cat> + && common_reference_with&, + const remove_reference_t<_Up>&> + && three_way_comparable< + common_reference_t&, + const remove_reference_t<_Up>&>, _Cat> + && requires(const remove_reference_t<_Tp>& __t, + const remove_reference_t<_Up>& __u) { + { __t <=> __u } -> __detail::__compares_as<_Cat>; + { __u <=> __t } -> __detail::__compares_as<_Cat>; + }; +#endif + + template + using __cmp2way_res_t + = decltype(std::declval<_Tp&>() <=> std::declval<_Up&>()); + + template + struct __cmp3way_helper + { }; + + template + struct __cmp3way_helper<_Tp, _Up, void_t<__cmp2way_res_t<_Tp, _Up>>> + { + using type = __cmp2way_res_t<_Tp, _Up>; + using __type = type; + }; + + /// [cmp.result], result of three-way comparison + template + struct compare_three_way_result + : __cmp3way_helper<_Tp, _Up> + { }; + + template + using compare_three_way_result_t + = typename compare_three_way_result<_Tp, _Up>::__type; + + // [cmp.object], typename compare_three_way + struct compare_three_way + { + // TODO +#if 0 + template + requires (three_way_comparable_with<_Tp, _Up> + || BUILTIN-PTR-THREE-WAY(_Tp, _Up)) + constexpr auto + operator()(_Tp&& __t, _Up&& __u) const noexcept + { + // TODO + } +#endif + + using is_transparent = void; + }; + + // [cmp.alg], comparison algorithms + inline namespace __cmp_alg + { + // TODO +#if 0 + inline constexpr unspecified strong_order = unspecified; + inline constexpr unspecified weak_order = unspecified; + inline constexpr unspecified partial_order = unspecified; + inline constexpr unspecified compare_strong_order_fallback = unspecified; + inline constexpr unspecified compare_weak_order_fallback = unspecified; + inline constexpr unspecified compare_partial_order_fallback = unspecified; +#endif + } +} + +#pragma GCC visibility pop + +#endif // C++20 + +#endif // _COMPARE diff --git a/libstdc++-v3/testsuite/std/concepts/concepts.object/regular.cc b/libstdc++-v3/testsuite/std/concepts/concepts.object/regular.cc index 338cf1ba32c..938b75e39d9 100644 --- a/libstdc++-v3/testsuite/std/concepts/concepts.object/regular.cc +++ b/libstdc++-v3/testsuite/std/concepts/concepts.object/regular.cc @@ -52,7 +52,7 @@ static_assert( ! std::regular ); struct HasEq { }; bool operator==(HasEq, HasEq) { return true; } -#ifdef __cpp_lib_three_way_comparison +#ifdef __cpp_impl_three_way_comparison static_assert( std::regular ); #else static_assert( ! std::regular );