为什么 std::is_invocable_r 拒绝返回不可移动类型的函数?

jac*_*bsa 13 c++ sfinae type-traits language-lawyer c++20

我很好奇的定义std::is_invocable_r以及它如何与不可移动类型交互。根据我对它应该模拟的语言规则的理解,它在 C++20 模式下 clang 下的 libc++ 实现似乎是错误的,所以我想知道我的理解有什么不正确。

\n

假设我们有一个不能移动或复制构造的类型,以及一个返回它的函数:

\n
struct CantMove {\n  CantMove() = default;\n  CantMove(CantMove&&) = delete;\n};\n\nstatic_assert(!std::is_move_constructible_v<CantMove>);\nstatic_assert(!std::is_copy_constructible_v<CantMove>);\n\nCantMove MakeCantMove() { return CantMove(); }\n
Run Code Online (Sandbox Code Playgroud)\n

然后可以调用该函数来初始化对象CantMove(我相信由于复制省略规则):

\n
CantMove cant_move = MakeCantMove();\n
Run Code Online (Sandbox Code Playgroud)\n

并且类型特征同意该函数是可调用的并返回CantMove

\n
using F = decltype(MakeCantMove);\nstatic_assert(std::is_invocable_v<F>);\nstatic_assert(std::is_same_v<CantMove, std::invoke_result_t<F>>);\n
Run Code Online (Sandbox Code Playgroud)\n

std::is_invocable_r表示不可能调用它来产生可转换为 的东西CantMove,至少在 C++20 下 clang 中是这样:

\n
static_assert(!std::is_invocable_r_v<CantMove, F>);\n
Run Code Online (Sandbox Code Playgroud)\n
\n

的定义是std::is_invocable_r

\n
\n

当被视为未计算的操作数时,该表达式INVOKE<R>(declval<Fn>(), declval<ArgTypes>()...)是格式良好的

\n
\n

INVOKE<R>定义为

\n
\n

定义INVOKE<R>(f, t1, t2, \xe2\x80\xa6, tN)为 [...]INVOKE(f, t1, t2, \xe2\x80\xa6, tN)隐式转换为R.

\n
\n

并(在本例中)简单地INVOKE 定义MakeCantMove()为. 但是隐式转换是否可能的定义是这样的:

\n
\n

对于某些发明的临时变量([dcl.init]) ,当且仅当声明格式良好时,表达式E可以隐式转换为类型。TT t=E;t

\n
\n

但我们在上面看到CantMove cant_move = MakeCantMove();编译器接受了。那么 clang 接受这个初始化是错误的,还是执行std::is_invocable_r_v错误的?或者我对标准的解读是错误的?

\n

根据记录,我关心这个问题的原因是像这样的类型std::move_only_function(我正在使用 C++20 的高级端口)其成员的重载集受限制std::is_invocable_r_v,并且我发现它\ 不可能有效地使用返回这样的无移动类型的函数。这是设计使然吗?如果是这样,为什么?

\n

康桓瑋*_*康桓瑋 13

那么 clang 接受这个初始化是错误的,还是执行std::is_invocable_r_v错误的?

这是libc++的一个bug。在的实现is_invocable_r中,它用于is_convertible确定结果是否可以隐式转换为T,这是不正确的,因为is_convertible_v<T, T>false对于不可移动类型,在这种情况下std::declval会添加对 的右值引用T

值得注意的是,libstdc++MSVC-STL都有关于此问题的错误报告,并且已修复。