Implement C++20 operator<=>.

There are three major pieces to this support: scalar operator<=>,
synthesis of comparison operators, and rewritten/reversed overload
resolution (e.g. a < b becomes 0 > b <=> a).

Unlike other defaulted functions, where we use synthesized_method_walk to
semi-simulate what the definition of the function will be like, this patch
determines the characteristics of a comparison operator by trying to define
it.

My handling of non-dependent rewritten operators in templates can still use
some work: build_min_non_dep_op_overload can't understand the rewrites and
crashes, so I'm avoiding it for now by clearing *overload.  This means we'll
do name lookup again at instantiation time, which can incorrectly mean a
different result.  I'll poke at this more in stage 3.

I'm leaving out a fourth section ("strong structural equality") even though
I've implemented it, because it seems likely to change radically tomorrow.

Thanks to Tim van Deurzen and Jakub for implementing lexing of the <=>
operator, and Jonathan for the initial <compare> header.

gcc/cp/
	* cp-tree.h (struct lang_decl_fn): Add maybe_deleted bitfield.
	(DECL_MAYBE_DELETED): New.
	(enum special_function_kind): Add sfk_comparison.
	(LOOKUP_REWRITTEN, LOOKUP_REVERSED): New.
	* call.c (struct z_candidate): Add rewritten and reversed methods.
	(add_builtin_candidate): Handle SPACESHIP_EXPR.
	(add_builtin_candidates): Likewise.
	(add_candidates): Don't add a reversed candidate if the parms are
	the same.
	(add_operator_candidates): Split out from build_new_op_1.  Handle
	rewritten and reversed candidates.
	(add_candidate): Swap conversions of reversed candidate.
	(build_new_op_1): Swap them back.  Build a second operation for
	rewritten candidates.
	(extract_call_expr): Handle rewritten calls.
	(same_fn_or_template): New.
	(joust): Handle rewritten and reversed candidates.
	* class.c (add_implicitly_declared_members): Add implicit op==.
	(classtype_has_op, classtype_has_defaulted_op): New.
	* constexpr.c (cxx_eval_binary_expression): Handle SPACESHIP_EXPR.
	(cxx_eval_constant_expression, potential_constant_expression_1):
	Likewise.
	* cp-gimplify.c (genericize_spaceship): New.
	(cp_genericize_r): Use it.
	* cp-objcp-common.c (cp_common_init_ts): Handle SPACESHIP_EXPR.
	* decl.c (finish_function): Handle deleted function.
	* decl2.c (grokfield): SET_DECL_FRIEND_CONTEXT on defaulted friend.
	(mark_used): Check DECL_MAYBE_DELETED.  Remove assumption that
	defaulted functions are non-static members.
	* error.c (dump_expr): Handle SPACESHIP_EXPR.
	* method.c (type_has_trivial_fn): False for sfk_comparison.
	(enum comp_cat_tag, struct comp_cat_info_t): New types.
	(comp_cat_cache): New array variable.
	(lookup_comparison_result, lookup_comparison_category)
	(is_cat, cat_tag_for, spaceship_comp_cat)
	(spaceship_type, genericize_spaceship)
	(common_comparison_type, early_check_defaulted_comparison)
	(comp_info, build_comparison_op): New.
	(synthesize_method): Handle sfk_comparison.  Handle deleted.
	(get_defaulted_eh_spec, maybe_explain_implicit_delete)
	(explain_implicit_non_constexpr, implicitly_declare_fn)
	(defaulted_late_check, defaultable_fn_check): Handle sfk_comparison.
	* name-lookup.c (get_std_name_hint): Add comparison categories.
	* tree.c (special_function_p): Add sfk_comparison.
	* typeck.c (cp_build_binary_op): Handle SPACESHIP_EXPR.

2019-11-05  Tim van Deurzen  <tim@kompiler.org>

	Add new tree code for the spaceship operator.
gcc/cp/
	* cp-tree.def: Add new tree code.
	* operators.def: New binary operator.
	* parser.c: Add new token and tree code.
libcpp/
	* cpplib.h: Add spaceship operator for C++.
	* lex.c: Implement conditional lexing of spaceship operator for C++20.

2019-11-05  Jonathan Wakely  <jwakely@redhat.com>

libstdc++-v3/
	* libsupc++/compare: New header.
	* libsupc++/Makefile.am (std_HEADERS): Add compare.
	* include/std/version: Define __cpp_lib_three_way_comparison.
	* include/std/functional: #include <compare>.

From-SVN: r277865
This commit is contained in:
Jason Merrill 2019-11-05 18:56:18 -05:00
parent b63566a404
commit b7689b962d
56 changed files with 2653 additions and 127 deletions

View File

@ -990,6 +990,7 @@ c_cpp_builtins (cpp_reader *pfile)
cpp_define (pfile, "__cpp_nontype_template_parameter_class=201806L");
cpp_define (pfile, "__cpp_impl_destroying_delete=201806L");
cpp_define (pfile, "__cpp_constexpr_dynamic_alloc=201907L");
cpp_define (pfile, "__cpp_impl_three_way_comparison=201907L");
}
if (flag_concepts)
{

View File

@ -1,3 +1,59 @@
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

View File

@ -514,6 +514,9 @@ struct z_candidate {
/* The flags active in add_candidate. */
int flags;
bool rewritten () { return (flags & LOOKUP_REWRITTEN); }
bool reversed () { return (flags & LOOKUP_REVERSED); }
};
/* Returns true iff T is a null pointer constant in the sense of
@ -2106,6 +2109,11 @@ add_candidate (struct z_candidate **candidates,
cand->flags = flags;
*candidates = cand;
if (convs && cand->reversed ())
/* Swap the conversions for comparison in joust; we'll swap them back
before build_over_call. */
std::swap (convs[0], convs[1]);
return cand;
}
@ -2737,6 +2745,16 @@ add_builtin_candidate (struct z_candidate **candidates, enum tree_code code,
where LR is the result of the usual arithmetic conversions between
types L and R.
For every integral type T there exists a candidate operator function of
the form
std::strong_ordering operator<=>(T, T);
For every pair of floating-point types L and R, there exists a candidate
operator function of the form
std::partial_ordering operator<=>(L, R);
14For every pair of types T and I, where T is a cv-qualified or cv-
unqualified complete object type and I is a promoted integral type,
there exist candidate operator functions of the form
@ -2758,11 +2776,15 @@ add_builtin_candidate (struct z_candidate **candidates, enum tree_code code,
bool operator>=(T, T);
bool operator==(T, T);
bool operator!=(T, T);
R operator<=>(T, T);
where R is the result type specified in [expr.spaceship].
17For every pointer to member type T, there exist candidate operator
functions of the form
bool operator==(T, T);
bool operator!=(T, T); */
bool operator!=(T, T);
std::strong_equality operator<=>(T, T); */
case MINUS_EXPR:
if (TYPE_PTROB_P (type1) && TYPE_PTROB_P (type2))
@ -2780,6 +2802,11 @@ add_builtin_candidate (struct z_candidate **candidates, enum tree_code code,
break;
return;
/* This isn't exactly what's specified above for operator<=>, but it's
close enough. In particular, we don't care about the return type
specified above; it doesn't participate in overload resolution and it
doesn't affect the semantics of the built-in operator. */
case SPACESHIP_EXPR:
case EQ_EXPR:
case NE_EXPR:
if ((TYPE_PTRMEMFUNC_P (type1) && TYPE_PTRMEMFUNC_P (type2))
@ -3138,6 +3165,7 @@ add_builtin_candidates (struct z_candidate **candidates, enum tree_code code,
case LE_EXPR:
case GT_EXPR:
case GE_EXPR:
case SPACESHIP_EXPR:
enum_p = 1;
/* Fall through. */
@ -5740,6 +5768,15 @@ add_candidates (tree fns, tree first_arg, const vec<tree, va_gc> *args,
fn_args = non_static_args;
}
/* Don't bother reversing an operator with two identical parameters. */
else if (args->length () == 2 && (flags & LOOKUP_REVERSED))
{
tree parmlist = TYPE_ARG_TYPES (TREE_TYPE (fn));
if (same_type_p (TREE_VALUE (parmlist),
TREE_VALUE (TREE_CHAIN (parmlist))))
continue;
}
if (TREE_CODE (fn) == TEMPLATE_DECL)
add_template_candidate (candidates,
fn,
@ -5800,6 +5837,178 @@ op_is_ordered (tree_code code)
}
}
/* Subroutine of build_new_op_1: Add to CANDIDATES all candidates for the
operator indicated by CODE/CODE2. This function calls itself recursively to
handle C++20 rewritten comparison operator candidates. */
static tree
add_operator_candidates (z_candidate **candidates,
tree_code code, tree_code code2,
vec<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,
@ -5809,7 +6018,7 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
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;
@ -5828,14 +6037,12 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
code2 = TREE_CODE (arg3);
arg3 = NULL_TREE;
}
tree fnname = ovl_op_identifier (ismodop, ismodop ? code2 : code);
tree arg1_type = unlowered_expr_type (arg1);
tree arg2_type = arg2 ? unlowered_expr_type (arg2) : NULL_TREE;
arg1 = prep_operand (arg1);
bool memonly = false;
switch (code)
{
case NEW_EXPR:
@ -5868,16 +6075,6 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
code_orig_arg2 = TREE_CODE (arg2_type);
break;
/* =, ->, [], () must be non-static member functions. */
case MODIFY_EXPR:
if (code2 != NOP_EXPR)
break;
/* FALLTHRU */
case COMPONENT_REF:
case ARRAY_REF:
memonly = true;
break;
default:
break;
}
@ -5908,82 +6105,10 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
/* Get the high-water mark for the CONVERSION_OBSTACK. */
p = conversion_obstack_alloc (0);
/* Add namespace-scope operators to the list of functions to
consider. */
if (!memonly)
{
tree fns = lookup_name_real (fnname, 0, 1, /*block_p=*/true, 0, 0);
fns = lookup_arg_dependent (fnname, fns, arglist);
add_candidates (fns, NULL_TREE, arglist, NULL_TREE,
NULL_TREE, false, NULL_TREE, NULL_TREE,
flags, &candidates, complain);
}
/* Add class-member operators to the candidate set. */
if (CLASS_TYPE_P (arg1_type))
{
tree fns;
fns = lookup_fnfields (arg1_type, fnname, 1);
if (fns == error_mark_node)
{
result = error_mark_node;
goto user_defined_result_ready;
}
if (fns)
add_candidates (BASELINK_FUNCTIONS (fns),
NULL_TREE, arglist, NULL_TREE,
NULL_TREE, false,
BASELINK_BINFO (fns),
BASELINK_ACCESS_BINFO (fns),
flags, &candidates, complain);
}
/* Per [over.match.oper]3.2, if no operand has a class type, then
only non-member functions that have type T1 or reference to
cv-qualified-opt T1 for the first argument, if the first argument
has an enumeration type, or T2 or reference to cv-qualified-opt
T2 for the second argument, if the second argument has an
enumeration type. Filter out those that don't match. */
else if (! arg2 || ! CLASS_TYPE_P (arg2_type))
{
struct z_candidate **candp, **next;
for (candp = &candidates; *candp; candp = next)
{
tree parmlist, parmtype;
int i, nargs = (arg2 ? 2 : 1);
cand = *candp;
next = &cand->next;
parmlist = TYPE_ARG_TYPES (TREE_TYPE (cand->fn));
for (i = 0; i < nargs; ++i)
{
parmtype = TREE_VALUE (parmlist);
if (TYPE_REF_P (parmtype))
parmtype = TREE_TYPE (parmtype);
if (TREE_CODE (unlowered_expr_type ((*arglist)[i])) == ENUMERAL_TYPE
&& (same_type_ignoring_top_level_qualifiers_p
(unlowered_expr_type ((*arglist)[i]), parmtype)))
break;
parmlist = TREE_CHAIN (parmlist);
}
/* No argument has an appropriate type, so remove this
candidate function from the list. */
if (i == nargs)
{
*candp = cand->next;
next = candp;
}
}
}
add_builtin_candidates (&candidates, code, code2, fnname, arglist,
flags, complain);
result = add_operator_candidates (&candidates, code, code2, arglist,
flags, complain);
if (result == error_mark_node)
goto user_defined_result_ready;
switch (code)
{
@ -6021,6 +6146,7 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
-fpermissive. */
else
{
tree fnname = ovl_op_identifier (ismodop, ismodop ? code2 : code);
const char *msg = (flag_permissive)
? G_("no %<%D(int)%> declared for postfix %qs,"
" trying prefix operator instead")
@ -6091,7 +6217,12 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
if (resolve_args (arglist, complain) == NULL)
result = error_mark_node;
else
result = build_over_call (cand, LOOKUP_NORMAL, complain);
{
if (cand->reversed ())
/* We swapped these in add_candidate, swap them back now. */
std::swap (cand->convs[0], cand->convs[1]);
result = build_over_call (cand, LOOKUP_NORMAL, complain);
}
if (trivial_fn_p (cand->fn))
/* There won't be a CALL_EXPR. */;
@ -6121,6 +6252,73 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
break;
}
}
/* If this was a C++20 rewritten comparison, adjust the result. */
if (cand->rewritten ())
{
/* FIXME build_min_non_dep_op_overload can't handle rewrites. */
if (overload)
*overload = NULL_TREE;
switch (code)
{
case EQ_EXPR:
gcc_checking_assert (cand->reversed ());
gcc_fallthrough ();
case NE_EXPR:
/* If a rewritten operator== candidate is selected by
overload resolution for an operator @, its return type
shall be cv bool.... */
if (TREE_CODE (TREE_TYPE (result)) != BOOLEAN_TYPE)
{
if (complain & tf_error)
{
auto_diagnostic_group d;
error_at (loc, "return type of %qD is not %qs",
cand->fn, "bool");
inform (loc, "used as rewritten candidate for "
"comparison of %qT and %qT",
arg1_type, arg2_type);
}
result = error_mark_node;
}
else if (code == NE_EXPR)
/* !(y == x) or !(x == y) */
result = build1_loc (loc, TRUTH_NOT_EXPR,
boolean_type_node, result);
break;
/* If a rewritten operator<=> candidate is selected by
overload resolution for an operator @, x @ y is
interpreted as 0 @ (y <=> x) if the selected candidate is
a synthesized candidate with reversed order of parameters,
or (x <=> y) @ 0 otherwise, using the selected rewritten
operator<=> candidate. */
case SPACESHIP_EXPR:
if (!cand->reversed ())
/* We're in the build_new_op call below for an outer
reversed call; we don't need to do anything more. */
break;
gcc_fallthrough ();
case LT_EXPR:
case LE_EXPR:
case GT_EXPR:
case GE_EXPR:
{
tree lhs = result;
tree rhs = integer_zero_node;
if (cand->reversed ())
std::swap (lhs, rhs);
result = build_new_op (loc, code,
LOOKUP_NORMAL|LOOKUP_REWRITTEN,
lhs, rhs, NULL_TREE,
NULL, complain);
}
break;
default:
gcc_unreachable ();
}
}
}
else
{
@ -6232,6 +6430,7 @@ build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
if (complain & tf_warning && warn_tautological_compare)
warn_tautological_cmp (loc, code, arg1, arg2);
/* Fall through. */
case SPACESHIP_EXPR:
case PLUS_EXPR:
case MINUS_EXPR:
case MULT_EXPR:
@ -6307,6 +6506,29 @@ extract_call_expr (tree call)
call = TREE_OPERAND (call, 0);
if (TREE_CODE (call) == TARGET_EXPR)
call = TARGET_EXPR_INITIAL (call);
if (cxx_dialect >= cxx2a)
switch (TREE_CODE (call))
{
/* C++20 rewritten comparison operators. */
case TRUTH_NOT_EXPR:
call = TREE_OPERAND (call, 0);
break;
case LT_EXPR:
case LE_EXPR:
case GT_EXPR:
case GE_EXPR:
case SPACESHIP_EXPR:
{
tree op0 = TREE_OPERAND (call, 0);
if (integer_zerop (op0))
call = TREE_OPERAND (call, 1);
else
call = op0;
}
break;
default:;
}
gcc_assert (TREE_CODE (call) == CALL_EXPR
|| TREE_CODE (call) == AGGR_INIT_EXPR
|| call == error_mark_node);
@ -10772,6 +10994,20 @@ joust_maybe_elide_copy (z_candidate *&cand)
return false;
}
/* True if cand1 and cand2 represent the same function or function
template. */
static bool
same_fn_or_template (z_candidate *cand1, z_candidate *cand2)
{
if (cand1->fn == cand2->fn)
return true;
if (!cand1->template_decl || !cand2->template_decl)
return false;
return (most_general_template (TI_TEMPLATE (cand1->template_decl))
== most_general_template (TI_TEMPLATE (cand2->template_decl)));
}
/* Compare two candidates for overloading as described in
[over.match.best]. Return values:
@ -10798,6 +11034,7 @@ joust (struct z_candidate *cand1, struct z_candidate *cand2, bool warn,
/* If we have two pseudo-candidates for conversions to the same type,
or two candidates for the same function, arbitrarily pick one. */
if (cand1->fn == cand2->fn
&& cand1->reversed () == cand2->reversed ()
&& (IS_TYPE_OR_DECL_P (cand1->fn)))
return 1;
@ -10917,6 +11154,21 @@ joust (struct z_candidate *cand1, struct z_candidate *cand2, bool warn,
if (winner && comp != winner)
{
if (same_fn_or_template (cand1, cand2))
{
/* Ambiguity between normal and reversed versions of the
same comparison operator; prefer the normal one.
https://lists.isocpp.org/core/2019/10/7438.php */
if (cand1->reversed ())
winner = -1;
else
{
gcc_checking_assert (cand2->reversed ());
winner = 1;
}
break;
}
winner = 0;
goto tweak;
}
@ -11046,6 +11298,21 @@ joust (struct z_candidate *cand1, struct z_candidate *cand2, bool warn,
return winner;
}
/* F2 is a rewritten candidate (12.4.1.2) and F1 is not, or F1 and F2 are
rewritten candidates, and F2 is a synthesized candidate with reversed
order of parameters and F1 is not. */
if (cand1->rewritten ())
{
if (!cand2->rewritten ())
return -1;
if (!cand1->reversed () && cand2->reversed ())
return 1;
if (cand1->reversed () && !cand2->reversed ())
return -1;
}
else if (cand2->rewritten ())
return 1;
/* F1 is generated from a deduction-guide (13.3.1.8) and F2 is not */
if (deduction_guide_p (cand1->fn))
{

View File

@ -3234,6 +3234,17 @@ add_implicitly_declared_members (tree t, tree* access_decls,
a virtual function from a base class. */
declare_virt_assop_and_dtor (t);
/* If the class definition does not explicitly declare an == operator
function, but declares a defaulted three-way comparison operator function,
an == operator function is declared implicitly. */
if (!classtype_has_op (t, EQ_EXPR))
if (tree space = classtype_has_defaulted_op (t, SPACESHIP_EXPR))
{
tree eq = implicitly_declare_fn (sfk_comparison, t, false, space,
NULL_TREE);
add_method (t, eq, false);
}
while (*access_decls)
{
tree using_decl = TREE_VALUE (*access_decls);
@ -5386,6 +5397,44 @@ classtype_has_depr_implicit_copy (tree t)
return NULL_TREE;
}
/* True iff T has a member or friend declaration of operator OP. */
bool
classtype_has_op (tree t, tree_code op)
{
tree name = ovl_op_identifier (op);
if (get_class_binding (t, name))
return true;
for (tree f = DECL_FRIENDLIST (TYPE_MAIN_DECL (t)); f; f = TREE_CHAIN (f))
if (FRIEND_NAME (f) == name)
return true;
return false;
}
/* If T has a defaulted member or friend declaration of OP, return it. */
tree
classtype_has_defaulted_op (tree t, tree_code op)
{
tree name = ovl_op_identifier (op);
for (ovl_iterator oi (get_class_binding (t, name)); oi; ++oi)
{
tree fn = *oi;
if (DECL_DEFAULTED_FN (fn))
return fn;
}
for (tree f = DECL_FRIENDLIST (TYPE_MAIN_DECL (t)); f; f = TREE_CHAIN (f))
if (FRIEND_NAME (f) == name)
for (tree l = FRIEND_DECLS (f); l; l = TREE_CHAIN (l))
{
tree fn = TREE_VALUE (l);
if (DECL_DEFAULTED_FN (fn))
return fn;
}
return NULL_TREE;
}
/* Nonzero if we need to build up a constructor call when initializing an
object of this class, either because it has a user-declared constructor
or because it doesn't have a default constructor (so we need to give an

View File

@ -2480,6 +2480,12 @@ cxx_eval_binary_expression (const constexpr_ctx *ctx, tree t,
else if (code == POINTER_PLUS_EXPR)
r = cxx_fold_pointer_plus_expression (ctx, t, lhs, rhs, non_constant_p,
overflow_p);
else if (code == SPACESHIP_EXPR)
{
r = genericize_spaceship (type, lhs, rhs);
r = cxx_eval_constant_expression (ctx, r, false, non_constant_p,
overflow_p);
}
if (r == NULL_TREE)
r = fold_binary_loc (loc, code, type, lhs, rhs);
@ -5226,6 +5232,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
case GE_EXPR:
case EQ_EXPR:
case NE_EXPR:
case SPACESHIP_EXPR:
case UNORDERED_EXPR:
case ORDERED_EXPR:
case UNLT_EXPR:
@ -7037,6 +7044,7 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now,
case GE_EXPR:
case EQ_EXPR:
case NE_EXPR:
case SPACESHIP_EXPR:
want_rval = true;
goto binary;

View File

@ -1144,6 +1144,17 @@ cp_fold_function (tree fndecl)
cp_walk_tree (&DECL_SAVED_TREE (fndecl), cp_fold_r, &pset, NULL);
}
/* Turn SPACESHIP_EXPR EXPR into GENERIC. */
static tree genericize_spaceship (tree expr)
{
iloc_sentinel s (cp_expr_location (expr));
tree type = TREE_TYPE (expr);
tree op0 = TREE_OPERAND (expr, 0);
tree op1 = TREE_OPERAND (expr, 1);
return genericize_spaceship (type, op0, op1);
}
/* Perform any pre-gimplification lowering of C++ front end trees to
GENERIC. */
@ -1574,6 +1585,10 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data)
genericize_break_stmt (stmt_p);
break;
case SPACESHIP_EXPR:
*stmt_p = genericize_spaceship (*stmt_p);
break;
case OMP_FOR:
case OMP_SIMD:
case OMP_DISTRIBUTE:

View File

@ -518,6 +518,7 @@ cp_common_init_ts (void)
MARK_TS_EXP (VEC_DELETE_EXPR);
MARK_TS_EXP (VEC_INIT_EXPR);
MARK_TS_EXP (VEC_NEW_EXPR);
MARK_TS_EXP (SPACESHIP_EXPR);
/* Fold expressions. */
MARK_TS_EXP (BINARY_LEFT_FOLD_EXPR);

View File

@ -255,6 +255,7 @@ DEFTREECODE (IMPLICIT_CONV_EXPR, "implicit_conv_expr", tcc_unary, 1)
DEFTREECODE (DOTSTAR_EXPR, "dotstar_expr", tcc_expression, 2)
DEFTREECODE (TYPEID_EXPR, "typeid_expr", tcc_expression, 1)
DEFTREECODE (NOEXCEPT_EXPR, "noexcept_expr", tcc_unary, 1)
DEFTREECODE (SPACESHIP_EXPR, "spaceship_expr", tcc_expression, 2)
/* A placeholder for an expression that is not type-dependent, but
does occur in a template. When an expression that is not

View File

@ -2695,7 +2695,8 @@ struct GTY(()) lang_decl_fn {
unsigned omp_declare_reduction_p : 1;
unsigned has_dependent_explicit_spec_p : 1;
unsigned immediate_fn_p : 1;
unsigned spare : 11;
unsigned maybe_deleted : 1;
unsigned spare : 10;
/* 32-bits padding on 64-bit host. */
@ -3137,6 +3138,11 @@ struct GTY(()) lang_decl {
#define DECL_HAS_DEPENDENT_EXPLICIT_SPEC_P(NODE) \
(LANG_DECL_FN_CHECK (NODE)->has_dependent_explicit_spec_p)
/* Nonzero for a defaulted FUNCTION_DECL for which we haven't decided yet if
it's deleted. */
#define DECL_MAYBE_DELETED(NODE) \
(LANG_DECL_FN_CHECK (NODE)->maybe_deleted)
/* True (in a FUNCTION_DECL) if NODE is a virtual function that is an
invalid overrider for a function from a base class. Once we have
complained about an invalid overrider we avoid complaining about it
@ -5203,6 +5209,7 @@ enum special_function_kind {
destroyed. */
sfk_conversion, /* A conversion operator. */
sfk_deduction_guide, /* A class template deduction guide. */
sfk_comparison, /* A comparison operator (e.g. ==, <, <=>). */
sfk_virtual_destructor /* Used by member synthesis fns. */
};
@ -5565,6 +5572,17 @@ enum overload_flags { NO_SPECIAL = 0, DTOR_FLAG, TYPENAME_FLAG };
#define LOOKUP_ALLOW_FLEXARRAY_INIT (LOOKUP_DELEGATING_CONS << 1)
/* Require constant initialization of a non-constant variable. */
#define LOOKUP_CONSTINIT (LOOKUP_ALLOW_FLEXARRAY_INIT << 1)
/* We're looking for either a rewritten comparison operator candidate or the
operator to use on the former's result. We distinguish between the two by
knowing that comparisons other than == and <=> must be the latter, as must
a <=> expression trying to rewrite to <=> without reversing. */
#define LOOKUP_REWRITTEN (LOOKUP_CONSTINIT << 1)
/* Reverse the order of the two arguments for comparison rewriting. First we
swap the arguments in add_operator_candidates, then we swap the conversions
in add_candidate (so that they correspond to the original order of the
args), then we swap the conversions back in build_new_op_1 (so they
correspond to the order of the args in the candidate). */
#define LOOKUP_REVERSED (LOOKUP_REWRITTEN << 1)
#define LOOKUP_NAMESPACES_ONLY(F) \
(((F) & LOOKUP_PREFER_NAMESPACES) && !((F) & LOOKUP_PREFER_TYPES))
@ -6371,6 +6389,8 @@ extern bool type_has_virtual_destructor (tree);
extern bool classtype_has_move_assign_or_move_ctor_p (tree, bool user_declared);
extern bool classtype_has_non_deleted_move_ctor (tree);
extern tree classtype_has_depr_implicit_copy (tree);
extern bool classtype_has_op (tree, tree_code);
extern tree classtype_has_defaulted_op (tree, tree_code);
extern bool type_build_ctor_call (tree);
extern bool type_build_dtor_call (tree);
extern void explain_non_literal_class (tree);
@ -7544,6 +7564,8 @@ extern tree composite_pointer_type (const op_location_t &,
extern tree merge_types (tree, tree);
extern tree strip_array_domain (tree);
extern tree check_return_expr (tree, bool *);
extern tree spaceship_type (tree, tsubst_flags_t = tf_warning_or_error);
extern tree genericize_spaceship (tree, tree, tree);
extern tree cp_build_binary_op (const op_location_t &,
enum tree_code, tree, tree,
tsubst_flags_t);

View File

@ -16793,6 +16793,13 @@ finish_function (bool inline_p)
}
}
if (DECL_DELETED_FN (fndecl))
{
DECL_INITIAL (fndecl) = error_mark_node;
DECL_SAVED_TREE (fndecl) = NULL_TREE;
goto cleanup;
}
// If this is a concept, check that the definition is reasonable.
if (DECL_DECLARED_CONCEPT_P (fndecl))
check_function_concept (fndecl);
@ -16939,6 +16946,7 @@ finish_function (bool inline_p)
if (!processing_template_decl && !DECL_IMMEDIATE_FUNCTION_P (fndecl))
cp_genericize (fndecl);
cleanup:
/* We're leaving the context of this function, so zap cfun. It's still in
DECL_STRUCT_FUNCTION, and we'll restore it in tree_rest_of_compilation. */
set_cfun (NULL);

View File

@ -927,6 +927,10 @@ grokfield (const cp_declarator *declarator,
}
else if (init == ridpointers[(int)RID_DEFAULT])
{
if (friendp)
/* ??? do_friend doesn't set this because funcdef_flag is false
for in-class defaulted functions. So set it here. */
SET_DECL_FRIEND_CONTEXT (value, current_class_type);
if (defaultable_fn_check (value))
{
DECL_DEFAULTED_FN (value) = 1;
@ -5471,6 +5475,17 @@ mark_used (tree decl, tsubst_flags_t complain)
if (TREE_CODE (decl) == CONST_DECL)
used_types_insert (DECL_CONTEXT (decl));
if (TREE_CODE (decl) == FUNCTION_DECL
&& DECL_MAYBE_DELETED (decl))
{
/* ??? Switch other defaulted functions to use DECL_MAYBE_DELETED? */
gcc_assert (special_function_p (decl) == sfk_comparison);
++function_depth;
synthesize_method (decl);
--function_depth;
}
if (TREE_CODE (decl) == FUNCTION_DECL
&& !maybe_instantiate_noexcept (decl, complain))
return false;
@ -5592,7 +5607,6 @@ mark_used (tree decl, tsubst_flags_t complain)
/* Is it a synthesized method that needs to be synthesized? */
if (TREE_CODE (decl) == FUNCTION_DECL
&& DECL_NONSTATIC_MEMBER_FUNCTION_P (decl)
&& DECL_DEFAULTED_FN (decl)
/* A function defaulted outside the class is synthesized either by
cp_finish_decl or instantiate_decl. */

View File

@ -2286,6 +2286,7 @@ dump_expr (cxx_pretty_printer *pp, tree t, int flags)
case GE_EXPR:
case EQ_EXPR:
case NE_EXPR:
case SPACESHIP_EXPR:
case EXACT_DIV_EXPR:
dump_binary_op (pp, OVL_OP_INFO (false, TREE_CODE (t))->name, t, flags);
break;

View File

@ -406,6 +406,7 @@ type_has_trivial_fn (tree ctype, special_function_kind sfk)
case sfk_virtual_destructor:
return !TYPE_HAS_NONTRIVIAL_DESTRUCTOR (ctype);
case sfk_inheriting_constructor:
case sfk_comparison:
return false;
default:
gcc_unreachable ();
@ -877,6 +878,588 @@ do_build_copy_assign (tree fndecl)
finish_compound_stmt (compound_stmt);
}
/* C++20 <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
@ -889,6 +1472,7 @@ synthesize_method (tree fndecl)
location_t save_input_location = input_location;
int error_count = errorcount;
int warning_count = warningcount + werrorcount;
special_function_kind sfk = special_function_p (fndecl);
/* Reset the source location, we might have been previously
deferred, and thus have saved where we were first needed. */
@ -930,6 +1514,12 @@ synthesize_method (tree fndecl)
else
finish_mem_initializers (NULL_TREE);
}
else if (sfk == sfk_comparison)
{
/* Pass tf_none so the function is just deleted if there's a problem. */
build_comparison_op (fndecl, tf_none);
need_body = false;
}
/* If we haven't yet generated the body of the function, just
generate an empty compound statement. */
@ -941,7 +1531,10 @@ synthesize_method (tree fndecl)
}
finish_function_body (stmt);
expand_or_defer_fn (finish_function (/*inline_p=*/false));
finish_function (/*inline_p=*/false);
if (!DECL_DELETED_FN (fndecl))
expand_or_defer_fn (fndecl);
input_location = save_input_location;
@ -1753,6 +2346,13 @@ get_defaulted_eh_spec (tree decl, tsubst_flags_t complain)
if (DECL_CLONED_FUNCTION_P (decl))
decl = DECL_CLONED_FUNCTION (decl);
special_function_kind sfk = special_function_p (decl);
if (sfk == sfk_comparison)
{
/* We're in synthesize_method. Start with NULL_TREE, build_comparison_op
will adjust as needed. */
gcc_assert (decl == current_function_decl);
return NULL_TREE;
}
tree ctype = DECL_CONTEXT (decl);
tree parms = FUNCTION_FIRST_USER_PARMTYPE (decl);
tree parm_type = TREE_VALUE (parms);
@ -1836,7 +2436,14 @@ maybe_explain_implicit_delete (tree decl)
informed = true;
}
}
if (!informed)
if (!informed && sfk == sfk_comparison)
{
inform (DECL_SOURCE_LOCATION (decl),
"%q#D is implicitly deleted because the default "
"definition would be ill-formed:", decl);
build_comparison_op (decl, tf_warning_or_error);
}
else if (!informed)
{
tree parms = FUNCTION_FIRST_USER_PARMTYPE (decl);
bool const_p = false;
@ -1891,10 +2498,18 @@ explain_implicit_non_constexpr (tree decl)
bool const_p = CP_TYPE_CONST_P (non_reference (TREE_VALUE (parms)));
tree inh = DECL_INHERITED_CTOR (decl);
bool dummy;
synthesized_method_walk (DECL_CLASS_CONTEXT (decl),
special_function_p (decl), const_p,
NULL, NULL, NULL, &dummy, true,
&inh, parms);
special_function_kind sfk = special_function_p (decl);
if (sfk == sfk_comparison)
{
DECL_DECLARED_CONSTEXPR_P (decl) = true;
build_comparison_op (decl, tf_warning_or_error);
DECL_DECLARED_CONSTEXPR_P (decl) = false;
}
else
synthesized_method_walk (DECL_CLASS_CONTEXT (decl),
sfk, const_p,
NULL, NULL, NULL, &dummy, true,
&inh, parms);
}
/* DECL is an instantiation of an inheriting constructor template. Deduce
@ -1933,12 +2548,12 @@ deduce_inheriting_ctor (tree decl)
/* Implicitly declare the special function indicated by KIND, as a
member of TYPE. For copy constructors and assignment operators,
CONST_P indicates whether these functions should take a const
reference argument or a non-const reference. Returns the
FUNCTION_DECL for the implicitly declared function. */
reference argument or a non-const reference.
Returns the FUNCTION_DECL for the implicitly declared function. */
tree
implicitly_declare_fn (special_function_kind kind, tree type,
bool const_p, tree inherited_ctor,
bool const_p, tree pattern_fn,
tree inherited_parms)
{
tree fn;
@ -1950,8 +2565,11 @@ implicitly_declare_fn (special_function_kind kind, tree type,
tree this_parm;
tree name;
HOST_WIDE_INT saved_processing_template_decl;
bool deleted_p;
bool constexpr_p;
bool deleted_p = false;
bool constexpr_p = false;
bool friend_p = (kind == sfk_comparison && DECL_FRIEND_P (pattern_fn));
tree inherited_ctor = (kind == sfk_inheriting_constructor
? pattern_fn : NULL_TREE);
/* Because we create declarations for implicitly declared functions
lazily, we may be creating the declaration for a member of TYPE
@ -1978,6 +2596,7 @@ implicitly_declare_fn (special_function_kind kind, tree type,
else
return_type = void_type_node;
int this_quals = TYPE_UNQUALIFIED;
switch (kind)
{
case sfk_destructor:
@ -2021,6 +2640,36 @@ implicitly_declare_fn (special_function_kind kind, tree type,
}
break;
}
case sfk_comparison:
/* If the class definition does not explicitly declare an == operator
function, but declares a defaulted three-way comparison operator
function, an == operator function is declared implicitly with the same
access as the three-way comparison operator function.
The implicitly-declared == operator for a class X is an inline member
and is defined as defaulted in the definition of X.
If the three-way comparison operator function is declared as a
non-static const member, the implicitly-declared == operator function
is a member of the form
bool X::operator==(const X&) const;
Otherwise, the implicitly-declared == operator function is of the form
friend bool operator==(const X&, const X&); */
/* No other comparison operator is implicitly declared. */
name = ovl_op_identifier (false, EQ_EXPR);
return_type = boolean_type_node;
rhs_parm_type = cp_build_qualified_type (type, TYPE_QUAL_CONST);
rhs_parm_type = cp_build_reference_type (rhs_parm_type, false);
parameter_types = tree_cons (NULL_TREE, rhs_parm_type, parameter_types);
if (friend_p)
parameter_types = tree_cons (NULL_TREE, rhs_parm_type, parameter_types);
this_quals = TYPE_QUAL_CONST;
break;
default:
gcc_unreachable ();
}
@ -2038,9 +2687,10 @@ implicitly_declare_fn (special_function_kind kind, tree type,
else if (cxx_dialect >= cxx11)
{
raises = noexcept_deferred_spec;
synthesized_method_walk (type, kind, const_p, NULL, &trivial_p,
&deleted_p, &constexpr_p, false,
&inherited_ctor, inherited_parms);
if (kind != sfk_comparison)
synthesized_method_walk (type, kind, const_p, NULL, &trivial_p,
&deleted_p, &constexpr_p, false,
&inherited_ctor, inherited_parms);
}
else
synthesized_method_walk (type, kind, const_p, &raises, &trivial_p,
@ -2062,7 +2712,9 @@ implicitly_declare_fn (special_function_kind kind, tree type,
type_set_nontrivial_flag (type, kind);
/* Create the function. */
fn_type = build_method_type_directly (type, return_type, parameter_types);
tree this_type = cp_build_qualified_type (type, this_quals);
fn_type = build_method_type_directly (this_type, return_type,
parameter_types);
if (raises)
{
if (raises != error_mark_node)
@ -2073,16 +2725,25 @@ implicitly_declare_fn (special_function_kind kind, tree type,
gcc_assert (seen_error ());
}
fn = build_lang_decl (FUNCTION_DECL, name, fn_type);
if (kind != sfk_inheriting_constructor)
if (kind == sfk_comparison)
{
DECL_SOURCE_LOCATION (fn) = DECL_SOURCE_LOCATION (pattern_fn);
DECL_MAYBE_DELETED (fn) = true;
}
else if (kind != sfk_inheriting_constructor)
DECL_SOURCE_LOCATION (fn) = DECL_SOURCE_LOCATION (TYPE_NAME (type));
if (!IDENTIFIER_CDTOR_P (name))
/* Assignment operator. */
DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) = OVL_OP_NOP_EXPR;
if (IDENTIFIER_OVL_OP_P (name))
{
const ovl_op_info_t *op = IDENTIFIER_OVL_OP_INFO (name);
DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) = op->ovl_op_code;
}
else if (IDENTIFIER_CTOR_P (name))
DECL_CXX_CONSTRUCTOR_P (fn) = true;
else
else if (IDENTIFIER_DTOR_P (name))
DECL_CXX_DESTRUCTOR_P (fn) = true;
else
gcc_unreachable ();
SET_DECL_ALIGN (fn, MINIMUM_METHOD_BOUNDARY);
@ -2097,6 +2758,13 @@ implicitly_declare_fn (special_function_kind kind, tree type,
retrofit_lang_decl (decl);
DECL_PARM_INDEX (decl) = DECL_PARM_LEVEL (decl) = 1;
DECL_ARGUMENTS (fn) = decl;
if (friend_p)
{
/* The second parm of friend op==. */
tree decl2 = copy_decl (decl);
DECL_CHAIN (decl) = decl2;
DECL_PARM_INDEX (decl2) = 2;
}
}
else if (kind == sfk_inheriting_constructor)
{
@ -2122,7 +2790,7 @@ implicitly_declare_fn (special_function_kind kind, tree type,
constexpr_p = DECL_DECLARED_CONSTEXPR_P (inherited_ctor);
}
/* Add the "this" parameter. */
this_parm = build_this_parm (fn, fn_type, TYPE_UNQUALIFIED);
this_parm = build_this_parm (fn, fn_type, this_quals);
DECL_CHAIN (this_parm) = DECL_ARGUMENTS (fn);
DECL_ARGUMENTS (fn) = this_parm;
@ -2141,6 +2809,12 @@ implicitly_declare_fn (special_function_kind kind, tree type,
set_linkage_according_to_type (type, fn);
if (TREE_PUBLIC (fn))
DECL_COMDAT (fn) = 1;
if (kind == sfk_comparison && !friend_p)
{
/* The implicit op== has the same access as the op<=>. */
TREE_PRIVATE (fn) = TREE_PRIVATE (pattern_fn);
TREE_PROTECTED (fn) = TREE_PROTECTED (pattern_fn);
}
rest_of_decl_compilation (fn, namespace_bindings_p (), at_eof);
gcc_assert (!TREE_USED (fn));
@ -2182,6 +2856,16 @@ defaulted_late_check (tree fn)
/* Complain about invalid signature for defaulted fn. */
tree ctx = DECL_CONTEXT (fn);
special_function_kind kind = special_function_p (fn);
if (kind == sfk_comparison)
{
/* If the function was declared constexpr, check that the definition
qualifies. Otherwise we can define the function lazily. */
if (DECL_DECLARED_CONSTEXPR_P (fn))
synthesize_method (fn);
return;
}
bool fn_const_p = (copy_fn_p (fn) == 2);
tree implicit_fn = implicitly_declare_fn (kind, ctx, fn_const_p,
NULL, NULL);
@ -2272,6 +2956,13 @@ defaultable_fn_check (tree fn)
else if (move_fn_p (fn))
kind = sfk_move_assignment;
}
else if (DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) >= OVL_OP_EQ_EXPR
&& DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) <= OVL_OP_SPACESHIP_EXPR)
{
kind = sfk_comparison;
if (!early_check_defaulted_comparison (fn))
return false;
}
if (kind == sfk_none)
{
@ -2293,7 +2984,7 @@ defaultable_fn_check (tree fn)
if (DECL_NAME (p))
TREE_NO_WARNING (p) = 1;
if (TYPE_BEING_DEFINED (DECL_CONTEXT (fn)))
if (current_class_type && TYPE_BEING_DEFINED (current_class_type))
/* Defer checking. */;
else if (!processing_template_decl)
defaulted_late_check (fn);

View File

@ -5592,6 +5592,12 @@ get_std_name_hint (const char *name)
{"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},

View File

@ -104,12 +104,16 @@ DEF_OPERATOR ("|", BIT_IOR_EXPR, "or", OVL_OP_FLAG_BINARY)
DEF_OPERATOR ("^", BIT_XOR_EXPR, "eo", OVL_OP_FLAG_BINARY)
DEF_OPERATOR ("<<", LSHIFT_EXPR, "ls", OVL_OP_FLAG_BINARY)
DEF_OPERATOR (">>", RSHIFT_EXPR, "rs", OVL_OP_FLAG_BINARY)
/* defaultable_fn_check relies on the ordering of the comparison operators. */
DEF_OPERATOR ("==", EQ_EXPR, "eq", OVL_OP_FLAG_BINARY)
DEF_OPERATOR ("!=", NE_EXPR, "ne", OVL_OP_FLAG_BINARY)
DEF_OPERATOR ("<", LT_EXPR, "lt", OVL_OP_FLAG_BINARY)
DEF_OPERATOR (">", GT_EXPR, "gt", OVL_OP_FLAG_BINARY)
DEF_OPERATOR ("<=", LE_EXPR, "le", OVL_OP_FLAG_BINARY)
DEF_OPERATOR (">=", GE_EXPR, "ge", OVL_OP_FLAG_BINARY)
DEF_OPERATOR ("<=>", SPACESHIP_EXPR, "ss", OVL_OP_FLAG_BINARY)
DEF_OPERATOR ("&&", TRUTH_ANDIF_EXPR, "aa", OVL_OP_FLAG_BINARY)
DEF_OPERATOR ("||", TRUTH_ORIF_EXPR, "oo", OVL_OP_FLAG_BINARY)
DEF_OPERATOR (",", COMPOUND_EXPR, "cm", OVL_OP_FLAG_BINARY)

View File

@ -1850,6 +1850,7 @@ enum cp_parser_prec
PREC_AND_EXPRESSION,
PREC_EQUALITY_EXPRESSION,
PREC_RELATIONAL_EXPRESSION,
PREC_SPACESHIP_EXPRESSION,
PREC_SHIFT_EXPRESSION,
PREC_ADDITIVE_EXPRESSION,
PREC_MULTIPLICATIVE_EXPRESSION,
@ -1921,6 +1922,8 @@ static const cp_parser_binary_operations_map_node binops[] = {
{ CPP_LSHIFT, LSHIFT_EXPR, PREC_SHIFT_EXPRESSION },
{ CPP_RSHIFT, RSHIFT_EXPR, PREC_SHIFT_EXPRESSION },
{ CPP_SPACESHIP, SPACESHIP_EXPR, PREC_SPACESHIP_EXPRESSION },
{ CPP_LESS, LT_EXPR, PREC_RELATIONAL_EXPRESSION },
{ CPP_GREATER, GT_EXPR, PREC_RELATIONAL_EXPRESSION },
{ CPP_LESS_EQ, LE_EXPR, PREC_RELATIONAL_EXPRESSION },
@ -15507,6 +15510,10 @@ cp_parser_operator (cp_parser* parser, location_t start_loc)
op = GE_EXPR;
break;
case CPP_SPACESHIP:
op = SPACESHIP_EXPR;
break;
case CPP_AND_AND:
op = TRUTH_ANDIF_EXPR;
break;

View File

@ -19003,6 +19003,7 @@ tsubst_copy_and_build (tree t,
case GE_EXPR:
case LT_EXPR:
case GT_EXPR:
case SPACESHIP_EXPR:
case MEMBER_REF:
case DOTSTAR_EXPR:
{

View File

@ -5042,6 +5042,9 @@ special_function_p (const_tree decl)
return sfk_conversion;
if (deduction_guide_p (decl))
return sfk_deduction_guide;
if (DECL_OVERLOADED_OPERATOR_CODE_RAW (decl) >= OVL_OP_EQ_EXPR
&& DECL_OVERLOADED_OPERATOR_CODE_RAW (decl) <= OVL_OP_SPACESHIP_EXPR)
return sfk_comparison;
return sfk_none;
}

View File

@ -4889,6 +4889,7 @@ cp_build_binary_op (const op_location_t &location,
case EQ_EXPR:
case NE_EXPR:
case SPACESHIP_EXPR:
if (code0 == VECTOR_TYPE && code1 == VECTOR_TYPE)
goto vector_compare;
if ((complain & tf_warning)
@ -4965,7 +4966,9 @@ cp_build_binary_op (const op_location_t &location,
warn_for_null_address (location, op1, complain);
}
else if ((code0 == POINTER_TYPE && code1 == POINTER_TYPE)
|| (TYPE_PTRDATAMEM_P (type0) && TYPE_PTRDATAMEM_P (type1)))
|| (code == SPACESHIP_EXPR
? TYPE_PTRMEM_P (type0) && TYPE_PTRMEM_P (type1)
: TYPE_PTRDATAMEM_P (type0) && TYPE_PTRDATAMEM_P (type1)))
result_type = composite_pointer_type (location,
type0, type1, op0, op1,
CPO_COMPARISON, complain);
@ -5358,6 +5361,55 @@ cp_build_binary_op (const op_location_t &location,
location);
}
if (code == SPACESHIP_EXPR)
{
iloc_sentinel s (location);
tree orig_type0 = TREE_TYPE (orig_op0);
tree_code orig_code0 = TREE_CODE (orig_type0);
tree orig_type1 = TREE_TYPE (orig_op1);
tree_code orig_code1 = TREE_CODE (orig_type1);
if ((orig_code0 == BOOLEAN_TYPE) != (orig_code1 == BOOLEAN_TYPE))
/* "If one of the operands is of type bool and the other is not, the
program is ill-formed." */
result_type = NULL_TREE;
else if (code0 == POINTER_TYPE && orig_code0 != POINTER_TYPE
&& code1 == POINTER_TYPE && orig_code1 != POINTER_TYPE)
/* We only do array/function-to-pointer conversion if "at least one of
the operands is of pointer type". */
result_type = NULL_TREE;
else if (orig_code0 == ENUMERAL_TYPE && orig_code1 == ENUMERAL_TYPE
&& !(same_type_ignoring_top_level_qualifiers_p
(orig_type0, orig_type1)))
/* "If both operands have arithmetic types, or one operand has integral
type and the other operand has unscoped enumeration type, the usual
arithmetic conversions are applied to the operands." So we don't do
arithmetic conversions if the operands both have enumeral type. */
result_type = NULL_TREE;
if (result_type)
build_type = spaceship_type (result_type, complain);
if (result_type && arithmetic_types_p)
{
/* If a narrowing conversion is required, other than from an integral
type to a floating point type, the program is ill-formed. */
bool ok = true;
if (TREE_CODE (result_type) == REAL_TYPE
&& INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (orig_op0)))
/* OK */;
else if (!check_narrowing (result_type, orig_op0, complain))
ok = false;
if (TREE_CODE (result_type) == REAL_TYPE
&& INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (orig_op1)))
/* OK */;
else if (!check_narrowing (result_type, orig_op1, complain))
ok = false;
if (!ok && !(complain & tf_error))
return error_mark_node;
}
}
if (!result_type)
{
if (complain & tf_error)

View File

@ -0,0 +1,6 @@
/* { dg-do preprocess } */
/* { dg-options "-std=c11" { target c } } */
#define A(x, y) x##y
A(<=, >) /* { dg-error "does not give a valid preprocessing token" "" { target { ! c++2a } } } */
A(<=>, >) /* { dg-error "does not give a valid preprocessing token" "" { target c++2a } } */

View File

@ -0,0 +1,8 @@
// { 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; }

View File

@ -0,0 +1,15 @@
// { dg-do compile { target c++2a } }
struct A
{
int i;
bool operator==(A a) const { return i == a.i; }
};
struct B
{
A a;
bool operator==(const B&) const = default; // { dg-error "A::operator==" }
};
constexpr bool x = B() == B(); // { dg-error "non-.constexpr" }

View File

@ -0,0 +1,17 @@
// { dg-do run { target c++2a } }
struct D
{
int i;
bool operator==(const D& x) const = default; // OK, returns x.i == y.i
bool operator!=(const D& z) const = default; // OK, returns !(*this == z)
};
#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
int main()
{
D d{42};
assert (d == d);
assert (!(d != d));
}

View File

@ -0,0 +1,24 @@
// { 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>();
}

View File

@ -0,0 +1,12 @@
// { dg-do compile { target c++2a } }
struct D
{
int i;
bool operator==(const D& x) const = default; // OK, returns x.i == y.i
bool operator!=(const D& z) const = default; // OK, returns !(*this == z)
};
constexpr D d{42};
static_assert (d == d);
static_assert (!(d != d));

View File

@ -0,0 +1,16 @@
// { dg-do compile { target c++2a } }
struct A {
bool operator==(const A&) const;
};
struct D
{
A i;
bool operator==(const D& x) const = default; // { dg-error "A::operator==" }
bool operator!=(const D& z) const = default; // { dg-error "D::operator==" }
};
constexpr D d{A()};
static_assert (d == d); // { dg-error "non-constant|constexpr" }
static_assert (!(d != d)); // { dg-error "non-constant|constexpr" }

View File

@ -0,0 +1,8 @@
// { dg-do compile { target c++2a } }
struct A {
int operator==(const A&) const = default; // { dg-error "return .bool" }
bool operator==(const A&, const A&) const = default; // { dg-error "exactly one" }
bool operator==(int) const = default; // { dg-error "parameter type" }
bool operator==(const A&) = default; // { dg-error "const" }
};

View File

@ -0,0 +1,10 @@
// { dg-do compile { target c++2a } }
struct A {
int &r; // { dg-message "reference" }
bool operator==(const A&) const = default; // { dg-message "deleted" }
};
int i;
A a { i };
bool b = a == a; // { dg-error "deleted" }

View File

@ -0,0 +1,10 @@
// { dg-do compile { target c++2a } }
struct A
{
union { int i; } // { dg-message "union" }
bool operator==(const A&) const = default; // { dg-message "deleted" }
};
A a { 42 };
bool b = a == a; // { dg-error "deleted" }

View File

@ -0,0 +1,10 @@
// { dg-do compile { target c++2a } }
union A
{
int i;
bool operator==(const A&) const = default; // { dg-message "union" }
};
A a { 42 };
bool b = a == a; // { dg-error "deleted" }

View File

@ -0,0 +1,5 @@
// Test that we suggest adding #include <compare>.
// { dg-do compile { target c++2a } }
auto x = 1<=>2; // { dg-error "" }
// { dg-message "<compare>" "" { target *-*-* } .-1 }

View File

@ -0,0 +1,7 @@
// { 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" }

View File

@ -0,0 +1,15 @@
// 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;
}

View File

@ -0,0 +1,93 @@
// { 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));
}
}

View File

@ -0,0 +1,41 @@
// { 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));
}

View File

@ -0,0 +1,11 @@
// { 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" }
}

View File

@ -0,0 +1,21 @@
// { 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));
}
}

View File

@ -0,0 +1,7 @@
// { 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);

View File

@ -0,0 +1,43 @@
// 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);
}

View File

@ -0,0 +1,113 @@
// 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));
}

View File

@ -0,0 +1,43 @@
// 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);
}

View File

@ -0,0 +1,48 @@
// 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);
}

View File

@ -0,0 +1,54 @@
// 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>();
}

View File

@ -0,0 +1,15 @@
// 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));

View File

@ -60,6 +60,7 @@ struct Y : virtual X
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
@ -81,7 +82,6 @@ Foo1 (T)
{ int t = x | I; assert (t == 7); }
{ int t = x && I; assert (t == 7); }
{ int t = x || I; assert (t == 7); }
{ int t = x != I; assert (t == 7); }
{ int t = x < I; assert (t == 7); }
{ int t = x <= I; assert (t == 7); }
{ int t = x > I; assert (t == 7); }
@ -104,6 +104,7 @@ Foo1 (T)
{ int t = x & I; assert (t == 8); }
{ int t = &x; assert (t == 8); }
{ int t = x == I; assert (t == 8); }
{ int t = x != I; assert (t == 8); }
}
template <typename T>
@ -204,7 +205,6 @@ Foo4 (T)
{ int t = x.operator| (I); assert (t == 7); }
{ int t = x.operator&& (I); assert (t == 7); }
{ int t = x.operator|| (I); assert (t == 7); }
{ int t = x.operator!= (I); assert (t == 7); }
{ int t = x.operator< (I); assert (t == 7); }
{ int t = x.operator<= (I); assert (t == 7); }
{ int t = x.operator> (I); assert (t == 7); }
@ -227,6 +227,7 @@ Foo4 (T)
{ int t = x.operator& (); assert (t == 8); }
{ int t = x.operator& (I); assert (t == 8); }
{ int t = operator== (x, I); assert (t == 8); }
{ int t = x.operator!= (I); assert (t == 8); }
}

View File

@ -11,18 +11,17 @@ public:
operator int() const {return 2;}
};
bool operator==(const MyInt& a, const int& b) // { dg-message "operator==" } candidate
bool operator==(const MyInt& a, const int& b) // { dg-message "operator==" "" { target c++17_down } }
{
return (int)a == b;
}
bool operator==(const MyInt& a, const MyInt& b) // { dg-message "operator==" } candidate
bool operator==(const MyInt& a, const MyInt& b) // { dg-message "operator==" "" { target c++17_down } }
{
return (int)a == (int)b;
}
bool f()
{
return 3 == MyInt(); // { dg-error "ambiguous" "err" }
// { dg-message "operator==" "match candidate text" { target *-*-* } .-1 }
return 3 == MyInt(); // { dg-error "ambiguous" "err" { target c++17_down } }
}

View File

@ -1,3 +1,8 @@
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

View File

@ -78,6 +78,7 @@ struct _cpp_file;
OP(NOT_EQ, "!=") \
OP(GREATER_EQ, ">=") \
OP(LESS_EQ, "<=") \
OP(SPACESHIP, "<=>") \
\
/* These two are unary + / - in preprocessor expressions. */ \
OP(PLUS_EQ, "+=") /* math */ \

View File

@ -2980,7 +2980,13 @@ _cpp_lex_direct (cpp_reader *pfile)
result->type = CPP_LESS;
if (*buffer->cur == '=')
buffer->cur++, result->type = CPP_LESS_EQ;
{
buffer->cur++, result->type = CPP_LESS_EQ;
if (*buffer->cur == '>'
&& CPP_OPTION (pfile, cplusplus)
&& CPP_OPTION (pfile, lang) >= CLK_GNUCXX2A)
buffer->cur++, result->type = CPP_SPACESHIP;
}
else if (*buffer->cur == '<')
{
buffer->cur++;
@ -3491,6 +3497,7 @@ cpp_avoid_paste (cpp_reader *pfile, const cpp_token *token1,
|| (CPP_OPTION (pfile, objc)
&& token1->val.str.text[0] == '@'
&& (b == CPP_NAME || b == CPP_STRING)));
case CPP_LESS_EQ: return c == '>';
case CPP_STRING:
case CPP_WSTRING:
case CPP_UTF8STRING:

View File

@ -1,5 +1,10 @@
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.

View File

@ -1385,8 +1385,9 @@ endif
# <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 \

View File

@ -66,6 +66,7 @@
#endif
#if __cplusplus > 201703L
# include <bits/range_cmp.h>
# include <compare>
#endif
namespace std _GLIBCXX_VISIBILITY(default)

View File

@ -187,6 +187,9 @@
#define __cpp_lib_list_remove_return_type 201806L
#define __cpp_lib_math_constants 201907L
#define __cpp_lib_span 201902L
#if __cpp_impl_three_way_comparison >= 201907L
# define __cpp_lib_three_way_comparison 201711L
#endif
#define __cpp_lib_to_array 201907L
#endif
#endif // C++2a

View File

@ -31,7 +31,7 @@ toolexeclib_LTLIBRARIES = libsupc++.la
noinst_LTLIBRARIES = libsupc++convenience.la
std_HEADERS = \
cxxabi.h exception initializer_list new typeinfo
compare cxxabi.h exception initializer_list new typeinfo
bits_HEADERS = \
atomic_lockfree_defines.h cxxabi_forced.h \

View File

@ -0,0 +1,644 @@
// -*- C++ -*- operator<=> three-way comparison support.
// Copyright (C) 2019 Free Software Foundation, Inc.
//
// This file is part of GCC.
//
// GCC is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3, or (at your option)
// any later version.
//
// GCC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.
// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
// <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

View File

@ -52,7 +52,7 @@ static_assert( ! std::regular<HasReference> );
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> );