为什么<T> LLVM 中的预期为 Expect&&... 实现两个构造函数<T>?

Why does Expected<T> in LLVM implement two constructors for Expected<T>&&?

本文关键字:gt lt 实现 构造函数 两个 LLVM 为什么 Expect      更新时间:2023-10-16

Expected<T>在 llvm/Support/Error.h 中实现。它是一个标记的联合,持有TError

Expected<T>是一个类型为T的模板类:

template <class T> class LLVM_NODISCARD Expected

但是这两个构造函数真的让我感到困惑:

/// Move construct an Expected<T> value from an Expected<OtherT>, where OtherT
/// must be convertible to T.
template <class OtherT>
Expected(Expected<OtherT> &&Other,
typename std::enable_if<std::is_convertible<OtherT, T>::value>::type
* = nullptr) {
moveConstruct(std::move(Other));
}
/// Move construct an Expected<T> value from an Expected<OtherT>, where OtherT
/// isn't convertible to T.
template <class OtherT>
explicit Expected(
Expected<OtherT> &&Other,
typename std::enable_if<!std::is_convertible<OtherT, T>::value>::type * =
nullptr) {
moveConstruct(std::move(Other));
}

为什么Expected<T>为同一个实现重复两个构造?为什么它不这样做?

template <class OtherT>
Expected(Expected<OtherT>&& Other) { moveConstruct(std::move(Other));}

因为根据提案,该构造函数是有条件显式的。这意味着构造函数仅在满足某些条件时才显式(此处为TOtherT的可转换性(。

C++在 C++20 之前没有此功能的机制(如explicit(condition)(。因此,实现需要使用其他一些机制,例如定义两个不同的构造函数(一个显式构造函数和另一个转换构造函数(,并确保根据条件选择正确的构造函数。这通常是在std::enable_if的帮助下通过SFINAE完成的,在那里病情得到解决。


自 C++20 起,应该有一个条件版本的explicit说明符。使用单个定义,实现会容易得多:

template <class OtherT>
explicit(!std::is_convertible_v<OtherT, T>)
Expected(Expected<OtherT> &&Other)
{
moveConstruct(std::move(Other));
}

要理解这一点,我们应该从std::is_convertible开始。根据 cpp 偏好:

如果虚函数定义To test() { return std::declval<From>(); }格式正确(即,可以使用隐式转换将std::declval<From>()转换为To,或者FromTo都可能是 cv 限定的 void(,则提供等于true的成员常量值。否则,值为false。出于此检查的目的,在 return 语句中使用std::declval不被视为 odr 使用。

访问检查的执行方式与从与任一类型无关的上下文中执行。仅考虑 return 语句中表达式的直接上下文的有效性(包括对返回类型的转换(。

这里重要的部分是它只检查隐式转换。因此,OP 中的两个实现的意思是,如果OtherT可隐式转换为T,则expected<OtherT>可隐式转换为expected<T>。如果OtherT需要显式强制转换来T,则Expected<OtherT>需要显式强制转换Expected<T>

以下是隐式和显式强制转换及其Expected对应项的示例

int x;
long int y = x;              // implicit cast ok
Expected<int> ex;
Expected<long int> ey = ex;  // also ok
void* v_ptr;
int* i_ptr = static_cast<int*>(v_ptr);              // explicit cast required
Expected<void*> ev_ptr;
auto ei_ptr = static_cast<Expected<int*>>(ev_ptr);  // also required