问题
With c++17 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 c++14 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).
回答1:
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.
回答2:
You are misusing the Invocable concept. This concept means nothing more than the ability to use std::invoke
on the given function and the provided arguments.
You can't do std::invoke(f, no_move(1))
, as this would provoke a copy/move of the forwarded argument. It is impossible for a prvalue to be used as a parameter through a forwarded call like invoke
. You can pass a prvalue to the forwarding call, but the eventual call to the given function will get an xvalue.
This is a good reason to avoid using immobile types as value parameters in functions. Take them by const&
instead.
C++ does not have a type trait to see if a function can be called with specific parameters in the way that you want.
来源:https://stackoverflow.com/questions/48174588/how-do-we-test-if-an-expression-of-a-certain-type-can-be-invoked-with-a-prvalue