为什么 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_r
的definition是
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
.
的右值引用
我很好奇 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_r
的definition是
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 toR
.
和INVOKE
defined (in this case) as simply MakeCantMove()
. But the definition关于是否可以进行隐式转换说:
An expression
E
can be implicitly converted to a typeT
if and only if the declarationT t=E;
is well-formed, for some invented temporary variablet
([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
.