我很好奇std::is_invocable_r的定义,以及它如何与不可移动的类型交互。基于我对它应该模仿的语言规则的理解,它在C++20模式下的clang实现似乎是错误的,所以我想知道我的理解有什么不正确的地方。
假设我们有一个不能移动或复制的类型,以及一个返回它的函数:
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();并且类型特征一致认为函数是可调用的,并返回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>);定义 of std::is_invocable_r是
如果将表达式
INVOKE<R>(declval<Fn>(), declval<ArgTypes>()...)视为未计算的操作数,则该表达式的格式很好。
INVOKE<R>是已定义作为
将
INVOKE<R>(f, t1, t2, …, tN)定义为.INVOKE(f, t1, t2, …, tN)隐式转换为R。
INVOKE 已定义 (在本例中)为简单的MakeCantMove()。但是,关于是否可能进行隐式转换的定义说:
表达式
E可以隐式转换为类型T当且仅当声明T t=E;格式良好时,对于某些发明的临时变量t(dcl.init)。
但是我们在上面看到,CantMove cant_move = MakeCantMove();被编译器所接受。接受这个初始化是错误的,还是std::is_invocable_r_v 的实现是错的?还是我对标准的解读错了?
作为记录,我关心这个问题的原因是,像std::move_only_function这样的类型(我使用的是它的C++20的高级端口),其成员的重载设置是由std::is_invocable_r_v设置的,而且我发现不可能使用像这样返回非移动类型的函数。这是故意的吗?如果是的话,原因何在?
发布于 2022-05-09 02:03:21
那么,接受这个初始化是错误的还是
std::is_invocable_r_v的实现是错的呢?
这是libc++的一个bug。在is_invocable_r中,它使用is_convertible来确定结果是否可以隐式转换为T,这是不正确的,因为is_convertible_v<T, T>是不可移动类型的false,在这种情况下,std::declval添加了对T的rvalue引用。
https://stackoverflow.com/questions/72166085
复制相似问题