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

Why does std::is_invocable_r reject functions returning non-moveable types?

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

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

struct CantMove {
  CantMove() = default;
  CantMove(CantMove&&) = delete;
};

static_assert(!std::is_move_constructible_v<CantMove>);
static_assert(!std::is_copy_constructible_v<CantMove>);

CantMove MakeCantMove() { return CantMove(); }

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

CantMove cant_move = MakeCantMove();

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

using F = decltype(MakeCantMove);
static_assert(std::is_invocable_v<F>);
static_assert(std::is_same_v<CantMove, std::invoke_result_t<F>>);

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

static_assert(!std::is_invocable_r_v<CantMove, F>);

std::is_invocable_rdefinition

The expression INVOKE<R>(declval<Fn>(), declval<ArgTypes>()...) is well-formed when treated as an unevaluated operand

INVOKE<R>defined

Define INVOKE<R>(f, t1, t2, …, tN) as [...] INVOKE(f, t1, t2, …, tN) implicitly converted to R.

INVOKE defined (in this case) as simply MakeCantMove(). But the definition关于是否可以进行隐式转换说:

An expression E can be implicitly converted to a type T if and only if the declaration T t=E; is well-formed, for some invented temporary variable t ([dcl.init]).

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

郑重声明,我关心这个问题的原因是像 std::move_only_function 这样的类型(我正在使用它的 C++20 的高级端口)有它们的成员重载集 restricted std::is_invocable_r_v,我发现不可能有用地使用 return 像这样的无移动类型的函数。这是设计使然吗?如果是,为什么?

So is clang wrong about accepting this initialization, or is the implementation of std::is_invocable_r_v wrong?

这是libc++的一个bug。在implementation of is_invocable_r中,它使用is_convertible来判断结果是否可以隐式转换为T,这是不正确的,因为is_convertible_v<T, T>false for [=25] =] 类型,在这种情况下 std::declval 添加对 T.

的右值引用

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