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)
{
+2019-11-05 Jason Merrill <jason@redhat.com>
+
+ 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 <tim@kompiler.org>
+
+ 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 <jason@redhat.com>
* call.c (build_new_op_1): Don't apply any standard conversions to
/* 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
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;
}
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
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))
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))
case LE_EXPR:
case GT_EXPR:
case GE_EXPR:
+ case SPACESHIP_EXPR:
enum_p = 1;
/* Fall through. */
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,
}
}
+/* 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<tree, va_gc> *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<tree,va_gc> *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,
vec<tree, va_gc> *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;
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:
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;
}
/* 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)
{
-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")
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. */;
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
{
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:
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);
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:
/* 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;
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;
}
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))
{
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);
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
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);
case GE_EXPR:
case EQ_EXPR:
case NE_EXPR:
+ case SPACESHIP_EXPR:
case UNORDERED_EXPR:
case ORDERED_EXPR:
case UNLT_EXPR:
case GE_EXPR:
case EQ_EXPR:
case NE_EXPR:
+ case SPACESHIP_EXPR:
want_rval = true;
goto binary;
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. */
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:
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);
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
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. */
#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
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. */
};
#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))
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);
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);
}
}
+ 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);
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);
}
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;
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;
/* 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. */
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;
case sfk_virtual_destructor:
return !TYPE_HAS_NONTRIVIAL_DESTRUCTOR (ctype);
case sfk_inheriting_constructor:
+ case sfk_comparison:
return false;
default:
gcc_unreachable ();
finish_compound_stmt (compound_stmt);
}
+/* C++20 <compare> 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 <compare> 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 ("%<std::%D%> 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 %<bool%>", 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 %<const%>", 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 %<const %T&%>", 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<tree> &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<tree> 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
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. */
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. */
}
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;
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);
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;
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
/* 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;
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
else
return_type = void_type_node;
+ int this_quals = TYPE_UNQUALIFIED;
switch (kind)
{
case sfk_destructor:
}
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 ();
}
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,
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)
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);
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)
{
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;
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));
/* 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);
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)
{
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);
{"atomic_ref", "<atomic>", cxx2a},
/* <bitset>. */
{"bitset", "<bitset>", cxx11},
+ /* <compare> */
+ {"weak_equality", "<compare>", cxx2a},
+ {"strong_equality", "<compare>", cxx2a},
+ {"partial_ordering", "<compare>", cxx2a},
+ {"weak_ordering", "<compare>", cxx2a},
+ {"strong_ordering", "<compare>", cxx2a},
/* <complex>. */
{"complex", "<complex>", cxx98},
{"complex_literals", "<complex>", cxx14},
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)
PREC_AND_EXPRESSION,
PREC_EQUALITY_EXPRESSION,
PREC_RELATIONAL_EXPRESSION,
+ PREC_SPACESHIP_EXPRESSION,
PREC_SHIFT_EXPRESSION,
PREC_ADDITIVE_EXPRESSION,
PREC_MULTIPLICATIVE_EXPRESSION,
{ 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 },
op = GE_EXPR;
break;
+ case CPP_SPACESHIP:
+ op = SPACESHIP_EXPR;
+ break;
+
case CPP_AND_AND:
op = TRUTH_ANDIF_EXPR;
break;
case GE_EXPR:
case LT_EXPR:
case GT_EXPR:
+ case SPACESHIP_EXPR:
case MEMBER_REF:
case DOTSTAR_EXPR:
{
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;
}
case EQ_EXPR:
case NE_EXPR:
+ case SPACESHIP_EXPR:
if (code0 == VECTOR_TYPE && code1 == VECTOR_TYPE)
goto vector_compare;
if ((complain & tf_warning)
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);
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)
--- /dev/null
+/* { 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 } } */
--- /dev/null
+// { dg-do compile { target c++17_down } }
+// { dg-options "-Wno-pointer-arith" }
+
+struct X {};
+bool operator<= (X, X);
+template<bool (X, X)> struct Y {};
+Y<&operator<=> y;
+bool foo (bool (*fn) (X, X), int n) { return n+&operator<=> fn; }
--- /dev/null
+// { 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" }
--- /dev/null
+// { 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));
+}
--- /dev/null
+// { dg-do run { target c++2a } }
+
+template <class T>
+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 <class T>
+void f()
+{
+ D<T> d{42};
+ assert (d == d);
+ assert (!(d != d));
+}
+
+int main()
+{
+ f<int>();
+}
--- /dev/null
+// { 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));
--- /dev/null
+// { 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" }
--- /dev/null
+// { 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" }
+};
--- /dev/null
+// { 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" }
--- /dev/null
+// { 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" }
--- /dev/null
+// { 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" }
--- /dev/null
+// Test that we suggest adding #include <compare>.
+// { dg-do compile { target c++2a } }
+
+auto x = 1<=>2; // { dg-error "" }
+// { dg-message "<compare>" "" { target *-*-* } .-1 }
--- /dev/null
+// { dg-do compile { target c++2a } }
+
+#include <compare>
+template <class T, T x = (T() <=> T())> // { dg-error "31:0 <=> 0" }
+void f(T);
+//constexpr int f(...) { return 42; }
+constexpr int i = f(24); // { dg-error "no match" }
--- /dev/null
+// This should continue to work.
+// { dg-do compile { target c++2a } }
+
+template<class T>
+struct A {
+ template<class U>
+ bool operator==(const A<U>&);
+};
+
+int main()
+{
+ A<int> a1;
+ A<void> a2;
+ return a1 == a2;
+}
--- /dev/null
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+#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));
+ }
+}
--- /dev/null
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+#define assert(X) do { if (!(X)) __builtin_abort(); } while(0)
+
+void f(){}
+void g(){}
+
+template <class T, class U, class R>
+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));
+}
--- /dev/null
+// { dg-do compile { target c++2a } }
+
+#include <compare>
+
+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" }
+}
--- /dev/null
+// { dg-do run { target c++2a } }
+// { dg-options "-fext-numeric-literals" }
+
+#include <compare>
+
+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));
+ }
+}
--- /dev/null
+// { dg-do compile { target c++2a } }
+
+// missing #include <compare>
+template <class T, T x = (T() <=> T()) == 0>
+void f(T);
+constexpr int f(...) { return 42; }
+constexpr int i = f(24);
--- /dev/null
+// Test with all operators explicitly defaulted.
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+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);
+}
--- /dev/null
+// Test with all operators explicitly defaulted.
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+template <class T>
+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 <class T>
+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 <class T>
+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 <class T, class U>
+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 <class T, class U>
+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<int> d{42};
+ constexpr D<int> d2{24};
+
+ static_assert (check_eq (d, d));
+ static_assert (check_less (d2, d));
+
+ constexpr E<float> e { 3.14 };
+ constexpr E<float> ee { 2.72 };
+ static_assert (check_eq (e, e));
+ static_assert (check_less (ee, e));
+
+ constexpr F<char> f { 'b' };
+ static_assert (check_eq (f, 'b'));
+ static_assert (check_less (f, 'c'));
+ static_assert (check_less ('a', f));
+}
--- /dev/null
+// Test with only spaceship defaulted.
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+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);
+}
--- /dev/null
+// Test for reversed candidates.
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+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);
+}
--- /dev/null
+// Test for reversed candidates.
+// { dg-do run { target c++2a } }
+
+#include <compare>
+
+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 <class T>
+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<int>();
+}
--- /dev/null
+// Test explicit weak_ordering.
+// { dg-do compile { target c++2a } }
+
+#include <compare>
+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 <decltype (c), const std::weak_ordering>);
+static_assert (std::is_eq (c));
+
template <typename T>
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
{ 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); }
{ 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 <typename 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); }
{ 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); }
}
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 } }
}
+2019-11-05 Tim van Deurzen <tim@kompiler.org>
+
+ * cpplib.h: Add spaceship operator for C++.
+ * lex.c: Implement conditional lexing of spaceship operator for C++20.
+
2019-10-31 Jakub Jelinek <jakub@redhat.com>
PR preprocessor/92296
OP(NOT_EQ, "!=") \
OP(GREATER_EQ, ">=") \
OP(LESS_EQ, "<=") \
+ OP(SPACESHIP, "<=>") \
\
/* These two are unary + / - in preprocessor expressions. */ \
OP(PLUS_EQ, "+=") /* math */ \
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++;
|| (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:
2019-11-05 Jonathan Wakely <jwakely@redhat.com>
+ * libsupc++/compare: New header.
+ * libsupc++/Makefile.am (std_HEADERS): Add compare.
+ * include/std/version: Define __cpp_lib_three_way_comparison.
+ * include/std/functional: #include <compare>.
+
* include/std/version [!_GLIBCXX_HOSTED]: Do not define feature test
macros for features that are only present in hosted builds.
# <new>, <typeinfo>, <exception>, <initializer_list>, <cstdalign>, <cstdarg>,
# <concepts>, <cstdbool>, <type_traits>, <bit>, <atomic>,
# and any files which they include (and which we provide).
-# <new>, <typeinfo>, <exception>, and <initializer_list> are installed by
-# libsupc++, so only the others and the sub-includes are copied here.
+# <new>, <typeinfo>, <exception>, <initializer_list> and <compare>
+# 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 \
#endif
#if __cplusplus > 201703L
# include <bits/range_cmp.h>
+# include <compare>
#endif
namespace std _GLIBCXX_VISIBILITY(default)
#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
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 \
--- /dev/null
+// -*- 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
+// <http://www.gnu.org/licenses/>.
+
+/** @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 <concepts>
+
+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<typename... _Ts>
+ struct common_comparison_category {
+ // using type = TODO
+ };
+
+ template<typename... _Ts>
+ using common_comparison_category_t
+ = typename common_comparison_category<_Ts...>::type;
+
+#if __cpp_concepts
+ namespace __detail
+ {
+ template<typename _Tp, typename _Cat>
+ concept __compares_as
+ = same_as<common_comparison_category_t<_Tp, _Cat>, _Cat>;
+
+ template<typename _Tp, typename _Up>
+ 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<typename _Tp, typename _Cat = partial_ordering>
+ 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<typename _Tp, typename _Up, typename _Cat = partial_ordering>
+ 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<_Tp>&,
+ const remove_reference_t<_Up>&>
+ && three_way_comparable<
+ common_reference_t<const remove_reference_t<_Tp>&,
+ 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<typename _Tp, typename _Up>
+ using __cmp2way_res_t
+ = decltype(std::declval<_Tp&>() <=> std::declval<_Up&>());
+
+ template<typename _Tp, typename _Up = _Tp, typename = void>
+ struct __cmp3way_helper
+ { };
+
+ template<typename _Tp, typename _Up>
+ 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<typename _Tp, typename _Up = _Tp>
+ struct compare_three_way_result
+ : __cmp3way_helper<_Tp, _Up>
+ { };
+
+ template<typename _Tp, typename _Up = _Tp>
+ 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<typename _Tp, typename _Up>
+ 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
struct HasEq { };
bool operator==(HasEq, HasEq) { return true; }
-#ifdef __cpp_lib_three_way_comparison
+#ifdef __cpp_impl_three_way_comparison
static_assert( std::regular<HasEq> );
#else
static_assert( ! std::regular<HasEq> );