Mike
Mike

Reputation: 702

Why does is_invocable work differently with reference parameters?

It seems to work differently only for this one case. What is going on here?

#include <type_traits>

void foo_value(int i){}
void foo_ref(int& i){}
void foo_cref(const int& i){}

int main()
{
    static_assert(std::is_invocable<decltype(foo_value), int>::value);
    static_assert(!std::is_invocable<decltype(foo_ref), int>::value); // what's wrong with this?
    static_assert(std::is_invocable<decltype(foo_cref), int>::value);
    static_assert(std::is_invocable<decltype(foo_value), int&>::value);
    static_assert(std::is_invocable<decltype(foo_ref), int&>::value);
    static_assert(std::is_invocable<decltype(foo_cref), int&>::value);
}

Upvotes: 2

Views: 666

Answers (2)

Brian Bi
Brian Bi

Reputation: 119382

The is_invocable and similar type traits allow you to specify not only the types of the arguments, but also their value categories. If T is some non-reference, non-void type, then for the purposes of is_invocable:

  • T means "rvalue of type T".
  • T& means "lvalue of type T".

Since foo_ref cannot be called with an rvalue of type int, the result of is_invocable is false when int is specified as the second argument.

Edit: This behaviour is specified in terms of std::declval. The expression std::declval<U>(), which is only allowed to appear inside an unevaluated expression, has return type std::add_rvalue_reference_t<U>, and according to the reference-collapsing rules, this means it is an lvalue if U is an lvalue reference type, and is otherwise an rvalue. The cppreference page for std::is_invocable mentions that both Fn and ArgTypes... are subject to this declval treatment, which means you also have the opportunity to specify whether the function-like entity is an lvalue or rvalue. If you want to know whether some other type trait also does this, look for which types, if any, appear in declval expressions.

Upvotes: 4

Guillaume Racicot
Guillaume Racicot

Reputation: 41780

It says that its not invocable that way... because it's indeed not invocable.

Try calling foo_ref with a int prvalue:

foo_ref(int{}); // does not compile

You'll see that this code is failing to compile, because a prvalue cannot be bound to a non const lvalue reference.

Upvotes: 4

Related Questions