From: Jason Merrill <jason@redhat.com>
To: Jakub Jelinek <jakub@redhat.com>
Cc: gcc-patches@gcc.gnu.org
Subject: Re: [PATCH] c++: Implement C++23 P1169R4 - static operator() [PR106651]
Date: Sat, 17 Sep 2022 13:30:08 +0200 [thread overview]
Message-ID: <98c787d4-6e2c-411b-0248-fd7a765e0acc@redhat.com> (raw)
In-Reply-To: <YyVsTZMB7ZaQDE6X@tucnak>
On 9/17/22 02:42, Jakub Jelinek wrote:
> On Sat, Sep 17, 2022 at 01:23:59AM +0200, Jason Merrill wrote:
>> On 9/13/22 12:42, Jakub Jelinek wrote:
>>> The following patch attempts to implement C++23 P1169R4 - static operator()
>>> paper's compiler side (there is some small library side too not implemented
>>> yet). This allows static members as user operator() declarations and
>>> static specifier on lambdas without lambda capture. As decl specifier
>>> parsing doesn't track about the presence and locations of all specifiers,
>>> the patch unfortunately replaces the diagnostics about duplicate mutable
>>> with diagnostics about conflicting specifiers because the information
>>> whether it was mutable mutable, mutable static, static mutable or static
>>> static is lost.
>>
>> I wonder why we don't give an error when setting the
>> conflicting_specifiers_p flag in cp_parser_set_storage_class? We should be
>> able to give a better diagnostic at that point.
>
> I will try that.
>
>>> Beyond this, the synthetized conversion operator changes
>>> for static lambdas as it can just return the operator() static method
>>> address, doesn't need to create a thunk for it.
>>> The change I'm least sure about is the call.cc (joust) change, one thing
>>> is that we ICEd because we assumed that len could be different only if
>>> both candidates are direct calls but it can be one direct and one indirect
>>> call,
>>
>> How do you mean?
>
> That is either on the
> struct less {
> static constexpr auto operator()(int i, int j) -> bool {
> return i < j;
> }
>
> using P = bool(*)(int, int);
> operator P() const { return operator(); }
> };
>
> static_assert(less{}(1, 2));
> testcase from the paper (S in static-operator-call1.C) or any static lambda
> which also has static operator() and cast operator.
> The paper then talks about
> operator()(contrived-parameter, int, int);
> call-function(bool(*)(int, int), int, int);
> which is exactly what caused the ICE, one of the cand?->fn was the static
> operator(), the other cand?->fn isn't a FUNCTION_DECL, but a function
> pointer (what the cast operator returns).
Ah, makes sense.
>>> + {
>>> + /* C++23 [over.best.ics.general] says:
>>> + When the parameter is the implicit object parameter of a static
>>> + member function, the implicit conversion sequence is a standard
>>> + conversion sequence that is neither better nor worse than any
>>> + other standard conversion sequence.
>>> + Apply this for C++23 or when the static member function is
>>> + overloaded call operator (C++23 feature we accept as an
>>> + extension). */
>>> + if ((cxx_dialect >= cxx23
>>> + || (DECL_OVERLOADED_OPERATOR_P (cand1->fn)
>>> + && DECL_OVERLOADED_OPERATOR_IS (cand1->fn, CALL_EXPR)))
>>> + && CONVERSION_RANK (cand2->convs[0]) >= cr_user)
>>> + winner = -1;
>>
>> I don't know what you're trying to do here. The above passage describes how
>> we've always compared static to non-static, which should work the same way
>> for operator().
>
> It doesn't work for the static operator() case vs. pointer returned by
> cast operator, with just the
> - int static_1 = DECL_STATIC_FUNCTION_P (cand1->fn);
> - int static_2 = DECL_STATIC_FUNCTION_P (cand2->fn);
> + int static_1 = (TREE_CODE (cand1->fn) == FUNCTION_DECL
> + && DECL_STATIC_FUNCTION_P (cand1->fn));
> + int static_2 = (TREE_CODE (cand2->fn) == FUNCTION_DECL
> + && DECL_STATIC_FUNCTION_P (cand2->fn));
>
> - if (DECL_CONSTRUCTOR_P (cand1->fn)
> + if (TREE_CODE (cand1->fn) == FUNCTION_DECL
> + && TREE_CODE (cand2->fn) == FUNCTION_DECL
> + && DECL_CONSTRUCTOR_P (cand1->fn)
> changes in joust so that it doesn't ICE, the call is ambiguous.
> Previously the standard had:
> "If F is a static member function, ICS1(F) is defined such that ICS1(F) is neither better
> nor worse than ICS1(G) for any function G, and, symmetrically, ICS1(G) is neither better
> nor worse than ICS1(F); otherwise,"
> but that was removed and instead:
> "When the parameter is the implicit object parameter of a static member function, the
> implicit conversion sequence is a standard conversion sequence that is neither better
> nor worse than any other standard conversion sequence."
> was added elsewhere. The code implements the former. We don't have any
> conversion for the non-existing object parameter to static, so what I want
> to do is keep doing what we were before if the other candidate's conversion
> on the first parameter is standard (CONVERSION_RANK (cand2->convs[0]) < cr_user)
> and otherwise indicate that the static operator() has there a standard
> conversion and so it should be better than any user/ellipsis/bad conversion.
> Now, it could be done just for cxx_dialect >= cxx23 as that is pedantically
> what the standard says, but if we allow with a pedwarn static operator()
> in earlier standards, I think we better treat it there the same, because
> otherwise one can't call any static lambdas (all those calls would be
> ambiguous).
Ah, OK. I don't think you need to check for C++23 or CALL_EXPR at all,
just CONVERSION_RANK of the other candidate conversion.
And I notice that you have winner = -1 in both cases; the first should
be winner = 1.
>>> @@ -1135,9 +1141,13 @@ maybe_add_lambda_conv_op (tree type)
>>> tree fn_args = NULL_TREE;
>>> {
>>> int ix = 0;
>>> - tree src = DECL_CHAIN (DECL_ARGUMENTS (callop));
>>> + tree src = DECL_ARGUMENTS (callop);
>>> tree tgt = NULL;
>>> + if (thisarg)
>>> + src = DECL_CHAIN (src);
>>
>> You could use FUNCTION_FIRST_USER_PARM to skip 'this' if present.
>
> Ok, will try.
>
>>> + else if (!decltype_call)
>>> + src = NULL_TREE;
>>> while (src)
>>> {
>>> tree new_node = copy_node (src);
>>> @@ -1160,12 +1170,15 @@ maybe_add_lambda_conv_op (tree type)
>>> if (generic_lambda_p)
>>> {
>>> tree a = tgt;
>>> - if (DECL_PACK_P (tgt))
>>> + if (thisarg)
>>
>> This is wrong, pack expansion handling has nothing to do with 'this'. Only
>> the assignment to CALL_EXPR_ARG should depend on thisarg.
>
> The point was that make_pack_expansion etc. is only used for what
> is later then stored to CALL_EXPR_ARG. As we don't need to construct
> call at all for the static lambda case, that looked like wasted effort.
Ah, you're right, I thought it was also used for decltype_call.
>>> @@ -1203,14 +1216,18 @@ maybe_add_lambda_conv_op (tree type)
>>> direct_argvec->address ());
>>> }
>>> - CALL_FROM_THUNK_P (call) = 1;
>>> - SET_EXPR_LOCATION (call, UNKNOWN_LOCATION);
>>> + if (thisarg)
>>> + {
>>> + CALL_FROM_THUNK_P (call) = 1;
>>> + SET_EXPR_LOCATION (call, UNKNOWN_LOCATION);
>>> + }
>>> - tree stattype = build_function_type (fn_result, FUNCTION_ARG_CHAIN (callop));
>>> - stattype = (cp_build_type_attribute_variant
>>> - (stattype, TYPE_ATTRIBUTES (optype)));
>>> - if (flag_noexcept_type
>>> - && TYPE_NOTHROW_P (TREE_TYPE (callop)))
>>> + tree stattype
>>> + = build_function_type (fn_result, thisarg ? FUNCTION_ARG_CHAIN (callop)
>>> + : TYPE_ARG_TYPES (optype));
>>> + stattype = cp_build_type_attribute_variant (stattype,
>>> + TYPE_ATTRIBUTES (optype));
>>> + if (flag_noexcept_type && TYPE_NOTHROW_P (TREE_TYPE (callop)))
>>> stattype = build_exception_variant (stattype, noexcept_true_spec);
>>
>> If !thisarg, shouldn't stattype just be optype? No need to rebuild it in
>> that case.
>
> I thought it should be auto-deduced if it isn't yet.
Ah, yes, of course.
You can use FUNCTION_FIRST_USER_PARMTYPE instead of the ?:.
The reformatting of the next two statements seems unnecessary.
> If it doesn't need to
> be, sure, we wouldn't need to bother with decltype_call for !thisarg
> either and just not do the loop on arguments etc.
> I've tried to create a testcase where I could see the generic static lambda
> but I failed to construct one, even with the template <...> lambda syntax
> I can't use a template id of the lambda to instantiate some particular case
> and then use *. Would passing a generic lambda to a function with
> some function pointer argument trigger it?
How about
void f()
{
auto l = [] (auto x) static { return x; };
int (*p)(int) = l;
}
?
Jason
next prev parent reply other threads:[~2022-09-17 11:30 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-09-13 16:42 Jakub Jelinek
2022-09-16 23:23 ` Jason Merrill
2022-09-17 6:42 ` Jakub Jelinek
2022-09-17 11:30 ` Jason Merrill [this message]
2022-09-19 12:25 ` [PATCH] c++, v2: " Jakub Jelinek
2022-09-27 2:27 ` Jason Merrill
2022-09-19 7:24 ` [PATCH] c++: Improve diagnostics about conflicting specifiers Jakub Jelinek
2022-09-27 0:28 ` Jason Merrill
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=98c787d4-6e2c-411b-0248-fd7a765e0acc@redhat.com \
--to=jason@redhat.com \
--cc=gcc-patches@gcc.gnu.org \
--cc=jakub@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).