c++c++17prvaluedeclvalinvocable

How do we test if an expression of a certain type can be invoked with a prvalue?


With we have fancy new is_invocable and fancy new prvalues that aren't really values.

This permits you to create an object without having to first logically construct it, then elide the construction.

I have run into a problem where using std::is_invocable to test if you can call something, and prvalue rules, seem to collide:

struct no_move {
  no_move(no_move&&)=delete;
  explicit no_move(int) {}
};
void f( no_move ) {}

now can we ask if f can be invoked using a prvalue of type no_move?

f( no_move(1) )

std::is_invocable< decltype(&f), no_move > doesn't work because it uses std::declval<no_move>() which is an xvalue like no_move&& not a prvalue of type no_move.

In this was the same, but guaranteed elision makes some functions callable with an xvalue (i.e., "T&&") and others with prvalues of type T.

Is there an alternative, or do we have to invent our own trait to handle this case?

(In a theoretical world where std::declval<T> returned T instead of T&&, is_invocable would, I believe, do the right thing).


Solution

  • Is there an alternative, or do we have to invent our own trait to handle this case?

    Yeah, you'd just have to write your own trait that doesn't use declval. Assuming you have std::is_detected lying around (which I know you certainly do):

    template <typename T> T make();
    
    template <typename F, typename... Args>
    using invoke_result_t = decltype(std::declval<F>()(make<Args>()...));
    //                               ^^^^^^^^^^^^^     ^^^^^
    
    template <typename F, typename... Args>
    using is_invocable = std::is_detected<invoke_result_t, F, Args...>;
    

    This way, std::is_invocable<decltype(f), no_move> is false_type, but is_invocable<decltype(f), no_move)> is true_type.

    I intentionally use declval<F>() for the function instead of make so as to allow using decltype(f) here. Really, invoke_result_t should be more complicated, and "do the right thing" for pointers to members, etc. But this is at least a simple approximation that indicates the viability of this approach.