Почему Expected <T>в LLVM реализует два конструктора для Expected <T>&&? - PullRequest
8 голосов
/ 20 октября 2019

Expected<T> реализовано в llvm / Support / Error.h. Это теговое объединение, содержащее либо T, либо Error.

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

Ответы [ 2 ]

8 голосов
/ 20 октября 2019

Поскольку этот конструктор является условно явным согласно предложению. Это означает, что конструктор является явным, только если выполняется некоторое условие (здесь конвертируемость T и OtherT).

C ++ не имеет механизма для этой функциональности (что-токак explicit(condition)) до C ++ 20. Реализации, таким образом, должны использовать какой-то другой механизм, такой как определение двух разных конструкторов - один явный и другой , преобразующий - и обеспечить выбор правильногоконструктор по условию. Обычно это делается через 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));
}
5 голосов
/ 20 октября 2019

Чтобы понять это, мы должны начать с std::is_convertible. Согласно cppreference :

Если определение мнимой функции To test() { return std::declval<From>(); } правильно сформировано (то есть, либо std::declval<From>() может быть преобразовано в To с помощью неявных преобразованийили оба From и To, возможно, являются cv-квалифицированными void), обеспечивает значение константы члена, равное true. В противном случае значение равно false. Для целей этой проверки использование std::declval в операторе возврата не считается odr-использованием.

Проверки доступа выполняются как будто из контекста, не связанного ни с одним из типов. Учитывается только допустимость непосредственного контекста выражения в операторе возврата (включая преобразования в тип возврата).

Важной частью здесь является то, что он проверяет только неявные преобразования. Следовательно, две реализации в вашем 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...