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, eitherstd::declval<From>()
can be converted toTo
using implicit conversions, or bothFrom
andTo
are possibly cv-qualified void), provides the member constant value equal totrue
. Otherwise value isfalse
. For the purposes of this check, the use ofstd::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