Reputation: 4732
Why does the standard not consider functors with a ref-qualified call operator to be invocable?
#include <concepts>
struct f { auto operator()() {} };
struct fr { auto operator()() & {} };
struct fcr { auto operator()() const& {} };
struct frr { auto operator()() && {} };
static_assert(std::copy_constructible<f>); // ok
static_assert(std::copy_constructible<fr>); // ok
static_assert(std::copy_constructible<fcr>); // ok
static_assert(std::copy_constructible<frr>); // ok
static_assert(std::invocable<f>); // ok
static_assert(std::invocable<fr>); // fails
static_assert(std::invocable<fcr>); // ok
static_assert(std::invocable<frr>); // ok
Well it might have something to do with std::declval
returning a temporary object, I don't feel an implementation detail as such should be relevant to the user. Semantically, the functors in the example code should not be regarded differently in terms of invocability.
Also, why does there appear to be this contradiction between what std::invocable
and std::function
consider to be a callable
object?
#include <functional>
using tf = decltype(std::function{f{}}); // ok
using tfr = decltype(std::function{fr{}}); // ok
using tfcr = decltype(std::function{fcr{}}); // ok
using tfrr = decltype(std::function{frr{}}); // fails
Just to muddy the waters even further, it also seems that MVSC v19.32 accepts the following code. Is that simply a compiler bug of MSVC?
template<std::invocable F>
auto g() -> void {}
using tg = decltype(g<f>); // ok
using tgr = decltype(g<fr>); // ok
using tgcr = decltype(g<fcr>); // ok
using tgrr = decltype(g<frr>); // ok
Upvotes: 4
Views: 345
Reputation: 303357
This fails:
static_assert(std::invocable<fr>); // fails
Because it is testing the validity of the expression std::invoke(std::declval<fr>())
, which is trying to invoke an rvalue of type fr
. But fr
's call operator is &
-qualified, which means you can only invoke it on an lvalue. That's why it's being rejected.
Why does the standard not consider functors with a ref-qualified call operator to be invocable?
It's not that it doesn't consider them to be invocable, period. It's that the value category also plays a role in std::invocable
. You can see that if you tried:
static_assert(std::invocable<fr&>); // ok
This is because now we're testing to see if fr
can be invoked as an lvalue (which it can), as opposed to as an rvalue (which it cannot).
Upvotes: 6