Skip to content

<functional>: Implement invoke_r - #2019

Merged
Stephan T. Lavavej (StephanTLavavej) merged 29 commits into
microsoft:mainfrom
SuperWig:invoke_r
Aug 17, 2021
Merged

<functional>: Implement invoke_r#2019
Stephan T. Lavavej (StephanTLavavej) merged 29 commits into
microsoft:mainfrom
SuperWig:invoke_r

Conversation

@SuperWig

@SuperWig Daniel Marshall (SuperWig) commented Jun 20, 2021

Copy link
Copy Markdown
Contributor

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? 😕

Comment thread stl/inc/functional Outdated
@SuperWig

Daniel Marshall (SuperWig) commented Jun 22, 2021

Copy link
Copy Markdown
Contributor Author

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
Copy Markdown
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

Daniel Marshall (SuperWig) commented Jun 22, 2021

Copy link
Copy Markdown
Contributor Author

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

Daniel Marshall (SuperWig) commented Jun 22, 2021

Copy link
Copy Markdown
Contributor Author

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
Copy Markdown
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

Daniel Marshall (SuperWig) commented Jun 23, 2021

Copy link
Copy Markdown
Contributor Author

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
Copy Markdown
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

Daniel Marshall (SuperWig) commented Jun 23, 2021

Copy link
Copy Markdown
Contributor Author

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.

Comment thread tests/std/tests/P2136R3_invoke_r/test.cpp Outdated
(probably should have tested this less confusing version first)
@SuperWig
Daniel Marshall (SuperWig) marked this pull request as ready for review June 23, 2021 11:54
@SuperWig
Daniel Marshall (SuperWig) requested a review from a team as a code owner June 23, 2021 11:54
Comment thread tests/std/tests/P2136R3_invoke_r/test.cpp Outdated
Comment thread tests/std/tests/P2136R3_invoke_r/test.cpp Outdated
@StephanTLavavej

This comment has been minimized.

* Use static_assert for type_traits
* Test against is_permissive instead of function template
* Reorganise tests slightly
@SuperWig

Copy link
Copy Markdown
Contributor Author

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
Copy Markdown
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
Copy Markdown
Contributor Author

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

@StephanTLavavej

Copy link
Copy Markdown
Member

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

@StephanTLavavej

Copy link
Copy Markdown
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
Copy Markdown
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.

Comment thread stl/inc/yvals_core.h
// 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
Copy Markdown
Contributor

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
Copy Markdown
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.

Comment thread stl/inc/functional Outdated
@StephanTLavavej

Copy link
Copy Markdown
Member

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

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