Clang LLVM C ++ `std :: необязательноудивительное сравнение поведения - PullRequest
0 голосов
/ 12 октября 2018

У меня есть функция load(std::optional<int> page), которая загружает данную страницу или все страницы, если page.empty().Поскольку загрузка является дорогостоящей операцией, я кэширую последнюю загруженную страницу и ее содержимое.Для этого я использую переменную-член типа std::optional<std::optional<int>>, значение которой должно указывать, кэшируется ли в данный момент одна страница, все страницы или вообще нет страниц.

Реализация libc ++ от LLVM (поставляется с clang Apple LLVM version 10.0.0 (clang-1000.11.45.2)) имеет удивительное поведение, когда дело касается сравнения std :: необязательных экземпляров, что отличается от того, что делает boost::optional (протестировано с 1.67):

std::cout << (std::optional<int>() == std::optional<std::optional<int>>()); // prints 1
std::cout << (boost::optional<int>() == boost::optional<boost::optional<int>>()); // prints 0

Что является правильным поведением и являетсяэто ошибка в реализации libc ++?

Ответы [ 3 ]

0 голосов
/ 12 октября 2018

Как примечание (но так как мы все равно здесь), я лично не слишком люблю ваш дизайн.Как потребитель вашего кода я бы ожидал, что load(<empty optional>) загрузится ... нет страницы.Это то, что необязательно должно означать.

Одно из решений состоит в том, чтобы иметь две разные функции:

void load(int);
void load_all();

Если это кажется вам слишком радикальным, вы можете выполнить перегрузки несколькими способами:

struct load_all{};

void load(int);
void load(load_all);

Мне действительно нравится быть откровенным о том, что я запрашиваю и что я получаю.Вот почему я настоятельно предпочитаю вышеизложенное следующему решению:

void load(int);
void load();

Для полноты, хотя я бы не рекомендовал его (поскольку инструменты для работы с std::variant громоздки, в противном случае это выражает намерениеочень хорошо):

struct load_all{};

void load(std::variant<int, load_all>);
0 голосов
/ 13 октября 2018

Это на самом деле довольно интересно ... поведение доброе, если двусмысленное.Нет четкого ответа на вопрос, что должно делать сравнение .И в зависимости от выбора реализации стандартная библиотека и Boost просто делают разные вещи.Никто не ошибается.

std::optional имеет смешанные сравнения.Это означает, что есть операторы, сравнивающие optional<T> с optional<U> и U.В этой модели наилучшее совпадение состоит в том, чтобы рассматривать обе стороны как необязательно - и, следовательно, они сравниваются одинаково, поскольку они оба отключены.

Но boost::optional имеет только сравнения одного типа.Это означает, что optional<T> сопоставимо только с optional<T> или T.В этой модели optional<int> интерпретируется как значение из optional<optional<int>>.Таким образом, у нас есть отключенный необязательный параметр и значение - поэтому они сравниваются неравно.

Я не уверен, что это сравнение действительно имеет четкое значение, поэтому лучше его избегать.

0 голосов
/ 12 октября 2018

Поведение корректно:

https://en.cppreference.com/w/cpp/utility/optional/operator_cmp

template< class T, class U > 
constexpr bool operator!=( const optional<T>& lhs, const optional<U>& rhs ); (2)

Выполняет операции сравнения с необязательными объектами.

1-6) Сравнивает два необязательных объектаLHS и RHS.Содержащиеся значения сравниваются (используя соответствующий оператор T), только если значения как lhs, так и rhs содержат.В противном случае

  • lhs считается равным rhs тогда и только тогда, когда lhs и rhs не содержат значения.

Конструктор по умолчанию для std::optional<T> создает объект, который не содержит значения, поэтому значения std::optional<int>>() и std::optional<std::optional<int>>() не содержат значения, поэтому они равны.

...