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

Because that constructor is conditionally explicit according to the proposal. This means that the constructor is explicit only if some condition is met (here, convertibility of T and OtherT).

C++ does not have a mechanism for this functionality (something as explicit(condition)) before C++20. Implementations thus need to use some other mechanism, such as a definition of two different constructors — one explicit and another one converting — and ensure the selection of the proper constructor according to the condition. This is typically done via SFINAE with the help of std::enable_if, where the condition is resolved.


Since C++20, there should be a conditional version of the explicit specifier. The implementation then would be much easier with a single definition:

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

To understand this we should start with std::is_convertible. According to cppreference:

If the imaginary function definition To test() { return std::declval<From>(); } is well-formed, (that is, either std::declval<From>() can be converted to To using implicit conversions, or both From and To are possibly cv-qualified void), provides the member constant value equal to true. Otherwise value is false. For the purposes of this check, the use of std::declval in the return statement is not considered an odr-use.

Access checks are performed as if from a context unrelated to either type. Only the validity of the immediate context of the expression in the return statement (including conversions to the return type) is considered.

The important part here is that it checks for implicit conversions only. Therefore what the two implementations in your OP mean is that if OtherT is implicitly convertible to T, then expected<OtherT> is implicitly convertible to expected<T>. If OtherT requires an explicit cast to T, then Expected<OtherT> requires and explicit cast to Expected<T>.

Here are examples of implicit and explicit casts and their Expected counterparts

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

Tags:

C++

Llvm