为什么LLVM中的Expected<T>实现了两个Expected<T>&&构造函数?

7

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));}

1
请注意 explicit 关键字。 - Mat
我想知道为什么这里很重要使用explicit关键字?谁能给一个例子吗? - yodahaji
2个回答

7
因为该构造函数在提案中被称为“有条件的显式构造函数”。这意味着只有在满足某些条件(即,T和OtherT之间可转换)时,该构造函数才是显式的。
在C++20之前,C++没有此功能的机制(例如,`explicit(condition)`)。因此,实现需要使用一些其他机制,例如定义两个不同的构造函数 - 一个是显式的,另一个是转换的,并根据条件确保选择正确的构造函数。通常使用SFINAE和`std::enable_if`的帮助来解决条件。
自C++20以来,应该有一个带有条件的`explicit`说明符版本。然后,使用单个定义的实现将变得更加容易。
template <class OtherT>
explicit(!std::is_convertible_v<OtherT, T>)
Expected(Expected<OtherT> &&Other)
{
   moveConstruct(std::move(Other));
}

谢谢你的回答。尽管我还是有些困惑,但在谷歌搜索“条件显式”后,我得到了很多资源。 - yodahaji
请注意,"conditionally explicit" 不是一个标准术语。它只是表示根据某些条件,构造函数是 "explicit" 或 "converting"。 - Daniel Langr

5
为了理解这个问题,我们应该从std::is_convertible开始。根据cppreference:
如果虚构的函数定义To test() { return std::declval<From>(); }是良好定义的(即std::declval<From>()可以通过隐式转换转换为To,或者FromTo可能具有cv限定符void),则提供等于true的成员常量值。否则值为false。对于这个检查,返回语句中使用std::declval不被认为是odr-use。
访问检查的执行方式类似于与任一类型无关的上下文。只考虑表达式在返回语句中的直接上下文的有效性(包括对返回类型的转换)。
这里重要的部分是它仅检查隐式转换。因此,你发帖中的两个实现意味着,如果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

谢谢您的回答。但是我不明白“然后Expected<OtherT>需要显式转换为Expected<T>”的含义是什么。这里的“显式转换”是什么意思?我无法想象一个例子。 - yodahaji
在帖子中添加了一些示例,以澄清显式转换的含义。通常,它们用于防止意外的隐式转换,因为这可能会引入错误。不幸的是,我现在无法测试代码,所以如果您发现有错别字/错误,请告诉我,我会进行修正。 - patatahooligan
这个语句“为了防止意外的隐式转换”回答了我的问题。谢谢:) - yodahaji

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接