Как мы можем проверить, можно ли вызывать выражение определенного типа с помощью prvalue?
С С++ 17 у нас есть fancy new is_invocable
и новые новые значения, которые на самом деле не являются значениями.
Это позволяет вам создать объект без необходимости его логического построения, а затем завершить конструкцию.
У меня возникла проблема, когда использование std::is_invocable
для проверки, если вы можете что-то вызывать, и правила prvalue, похоже, сталкиваются:
struct no_move {
no_move(no_move&&)=delete;
explicit no_move(int) {}
};
void f( no_move ) {}
теперь мы можем спросить, можно ли вызвать f
, используя prvalue типа no_move
?
f( no_move(1) )
std::is_invocable< decltype(&f), no_move >
не работает, поскольку использует std::declval<no_move>()
, который является значением x, как no_move&&
, а не значением класса no_move
.
В С++ 14 это было то же, но гарантированное elision делает некоторые функции вызываемыми с xvalue (т.е. "T&&
" ), а другие с prvalues типа T
.
Есть ли альтернатива, или нам нужно придумать свою собственную черту для обработки этого случая?
(В теоретическом мире, где std::declval<T>
вернул T
вместо T&&
, is_invocable
, я считаю, поступил правильно).
Ответы
Ответ 1
Есть ли альтернатива, или нам нужно придумать свою собственную черту для обработки этого случая?
Да, вам просто нужно написать свой собственный признак, который не использует declval
. Предполагая, что вы std::is_detected
лежат (что, как я знаю, вы, безусловно, делаете):
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...>;
Таким образом, std::is_invocable<decltype(f), no_move>
- false_type
, но is_invocable<decltype(f), no_move)>
- true_type
.
Я намеренно использую declval<F>()
для функции вместо make
, чтобы разрешить использование decltype(f)
здесь. Действительно, invoke_result_t
должен быть более сложным и "делать правильную вещь" для указателей на членов и т.д. Но это, по крайней мере, простое приближение, указывающее на жизнеспособность этого подхода.
Ответ 2
Вы злоупотребляете понятием Invocable. Эта концепция означает не что иное, как способность использовать std::invoke
для данной функции и предоставленные аргументы.
Вы не можете сделать std::invoke(f, no_move(1))
, так как это вызовет копирование/перемещение пересылаемого аргумента. Невозможно использовать значение prvalue в качестве параметра через переадресованный вызов типа invoke
. Вы можете передать prvalue на вызов переадресации, но возможный вызов данной функции получит значение x.
Это хорошая причина избежать использования неподвижных типов в качестве параметров значения в функциях. Возьмите их вместо const&
.
С++ не имеет характеристики типа, чтобы увидеть, можно ли вызвать функцию с определенными параметрами так, как вы хотите.