303
303

Reputation: 4732

Invocability of functors

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

Answers (1)

Barry
Barry

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

Related Questions