Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<functional>: Implement invoke_r #2019

Merged
merged 29 commits into from
Aug 17, 2021
Merged

Conversation

SuperWig
Copy link
Contributor

@SuperWig SuperWig commented Jun 20, 2021

Fixes #1978

It's a little light on tests (hence the draft PR) but at what point does it become a test of invoke rather than invoke_r?
The paper lists 3 things that invoke_r can do that invoke can't:

  1. In a call forwarder that allows specifying the return type or the full signature, putting void as the return type naturally discards the return value, as implied by std::is_invocable_r and std::is_nothrow_invocable_r.
  2. When R is not cv void, you can specify a compatible return type that is different from the callable entity. For example, you can request a function that returns T&& to return a prvalue of type T by calling invoke_r<T>.
  3. If the callable entity has overloaded call operators that may return different types, they may agree on a return type that allows you to specify. For example, you can perform an upcast for covariant return types.

I'm not sure how you would test 1, 2 I believe I have covered, and is doing the example from 3 going to be doing anything extra that isn't already covered by the simple long int test?

And I'm not sure why one test fails for noexcept. I assume it's due to /permissive but then why is __cpp_noexcept_function_type defined? 😕

@StephanTLavavej StephanTLavavej added the cxx23 C++23 feature label Jun 20, 2021
stl/inc/functional Outdated Show resolved Hide resolved
@SuperWig
Copy link
Contributor Author

SuperWig commented Jun 22, 2021

I always forget to check x86...

But apparently EDG thinks the single argument invoke_r is ambiguous? Just realised the void commit made the 3 argument overload a 2 argument.

Also not sure how to workaround the /permissive issue.

@StephanTLavavej
Copy link
Member

noexcept in the type system is orthogonal to /permissive versus strict mode, so something wacky is probably happening - we'll need to investigate.

@SuperWig
Copy link
Contributor Author

SuperWig commented Jun 22, 2021

Sorry, I forgot to update after adding that comment for fails /permissive that I did find out they were unrelated. Originally both were failing and after wrapping with the noexcept define didn't notice it was the other line it was talking about- it's actually why I added the comment, to remind my dumb self which line is which :P.

After some testing, it appears constexpr hates me again. Removing it fixed the issue.

#include <functional>

using namespace std;

int square(int n) {
    return n * n;
}
constexpr int square_noexcept(int n) noexcept {
    return n * n;
}

static_assert(!noexcept(invoke_r<int>(square, 3)));
static_assert(noexcept(invoke_r<int>(square_noexcept, 3)));

This compiles correctly. Adding constexpr causes it to fail.
Edit: Order of compiler switches matters?
/permissive /std:c++latest
/std:c++latest /permissive

Edit2: And the answer to that question is yes. Guess I'll file a bug report for "invoke with constexpr function == noexcept" (filed: DevCom-1457457)

@SuperWig
Copy link
Contributor Author

SuperWig commented Jun 22, 2021

I don't suppose there's a macro I can use to allow the static asserts in /permissive- mode?

And as a final thing before un-drafting the PR (unless the /permissive issue is blocking), should there be coverage for each test with all kinds of callables? Or perhaps one of each callable for at least one test (pretty sure I'm only missing using a data member).

@StephanTLavavej
Copy link
Member

I don't suppose there's a macro I can use to allow the static asserts in /permissive- mode?

The compiler intentionally does not provide a macro. We're able to detect it with this technique:

namespace detail {
static constexpr bool permissive() {
return false;
}
template <class>
struct DependentBase {
static constexpr bool permissive() {
return true;
}
};
template <class T>
struct Derived : DependentBase<T> {
static constexpr bool test() {
return permissive();
}
};
} // namespace detail
constexpr bool is_permissive = detail::Derived<int>::test();

should there be coverage for each test with all kinds of callables? Or perhaps one of each callable for at least one test (pretty sure I'm only missing using a data member).

Depends on the implementation. If (for whatever reason) you had to write a totally custom implementation, then intensive coverage of callable types would be strongly desirable. However, since you have a nicely layered implementation that calls std::invoke and then processes the return type, I think that your latter approach is sufficient/ideal - so the test can focus on extensively validating the return type scenarios (e.g. void, various conversions).

@SuperWig
Copy link
Contributor Author

SuperWig commented Jun 23, 2021

We're able to detect it with this technique:

That doesn't appear to be working. They still fail within an if constexpr (!is_permissive) block. Please tell me I didn't just run into another bug 😢.

Just tested a static_assert(is_permissive); and they all fail.

@StephanTLavavej
Copy link
Member

if constexpr generally needs a dependent condition in order to guard code that won’t compile, but I haven’t verified whether that will help here.

@SuperWig
Copy link
Contributor Author

SuperWig commented Jun 23, 2021

Oh right, duh. That makes sense; just going to use normal assert then.

Edit: actually how about this confusing condition?

static_assert(!noexcept(invoke_r<int>(square, 3)) == !is_permissive, "invoke_r<int>(square, 3) is noexcept");

Edit2: Actually come to think about it that is probably better (minus the double negation) as it would catch when the bug gets fixed.

(probably should have tested this less confusing version first)
@SuperWig SuperWig marked this pull request as ready for review June 23, 2021 11:54
@SuperWig SuperWig requested a review from a team as a code owner June 23, 2021 11:54
@SuperWig
Copy link
Contributor Author

SuperWig commented Jul 9, 2021

Would it be a good idea to mark invoke as nodiscard under C++23 with a message suggesting to use invoke_r<void> if they want to discard the value?

@AdamBucior
Copy link
Contributor

Would it be a good idea to mark invoke as nodiscard under C++23 with a message suggesting to use invoke_r<void> if they want to discard the value?

What's wrong with discarding return value of invoke?

@SuperWig
Copy link
Contributor Author

SuperWig commented Jul 9, 2021

Well having thought about it more probably not great unconditionally. Would be nice to be nodiscard if the callable is also nodiscard though.

@StephanTLavavej StephanTLavavej self-assigned this Jul 13, 2021
@StephanTLavavej
Copy link
Member

Yeah, too bad we can't detect and "perfectly forward" the nodiscard attribute.

@StephanTLavavej
Copy link
Member

I've pushed a merge with main, resolving trivial merge conflicts, and then some minor changes:

  • Use a lambda with an init-capture, so we can verify that invoke_r is invoking the function object in-place (instead of copying it and invoking the copy).
  • Rename count to lvalue since it's no longer needed by the lambda as a counter.
  • Include <string> because we're using the type string.
  • Consistently use the STATIC_ASSERT macro, instead of terse static_assert or classic static_assert (fixing occurrences of the latter that don't exactly describe the condition).

@StephanTLavavej
Copy link
Member

I'm mirroring this to an MSVC-internal PR. It's totally fine to push changes for code review feedback, but please notify me in that case.

@@ -267,6 +267,7 @@
// P1682R3 to_underlying() For Enumerations
// P1951R1 Default Template Arguments For pair's Forwarding Constructor
// P1989R2 Range Constructor For string_view
// P2136R3 invoke_r()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HUGE nit: the paper title does not seem to have the parenthesis

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch - this is intentional, I added the parentheses to the "cleaned-up" title in #1978 to be consistent with the other cleaned-up titles.

stl/inc/functional Outdated Show resolved Hide resolved
@StephanTLavavej StephanTLavavej merged commit 503823b into microsoft:main Aug 17, 2021
@StephanTLavavej
Copy link
Member

Thanks for implementing this pirate-themed invocation! Invoke arrr, matey! 🏴‍☠️ 😹 🎉

@SuperWig SuperWig deleted the invoke_r branch August 17, 2021 07:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cxx23 C++23 feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

P2136R3 invoke_r()
6 participants