Search
Write a publication
Pull to refresh

Comments 55

Выражение a == b никогда не будет вычисляться как operator<=>(a, b) == 0 неявно

имо зря убрали
UFO landed and left these words here
для strong_ordering то в чем проблема генерировать ==? Это же для других из эквивалентности может не следовать равенство.
На самом деле не убрали: eel.is/c++draft/class#compare.default-3
В начале статьи неточность, но ниже говорится что на самом деле будет использоваться operator<=> «Это единственный случай, когда компилятор сгенерирует оператор сравнения, который вы сами не писали.»

При чём здесь operator<=>? По вашей ссылке говорится — так же как и в статье — что при указанных условиях компилятор сгенерирует operator==, но последний будет определён как =default, а не через operator<=>.

ЕСЛИ (меньше-больше-или-равно )

Идея полезная, но синтаксис не просто режет глаз, это скорее штопор, в этот самый глаз вкручиваемый.
Оператор подёргивания (--i++) всё-равно лучше.
struct A {
T t;
U u;
V v;

T, U и V — это где-то в другом месте определенные типы данных? Очень уж давно учил, но подобный код встречал для идеи функции с жуткой рекурсией для определения «очень большого числа».
очень похоже на урезанное заимстование из Хаскеля класс-типов Равенства Eq и Порядка Ord
class Eq a where  
    (==) :: a -> a -> Bool  
    (/=) :: a -> a -> Bool  
    x == y = not (x /= y)  
    x /= y = not (x == y) 

Где достаточно определить либо равенство, либо неравенство, и второе будет вычислено как не-первое.
Операции сравнения:
class  (Eq a) => Ord a  where
  compare, (<), (<=), (>=), (>)  :: a -> a -> Bool
  max, min              :: a -> a -> a

Где минимально и достаточно определить лишь функции compare и (<=)

В известном смысле оно не урезанное, а улучшенное (и на самом деле не из Хаскелла, а из математики). Улучшенное потому, что как раз в Хаскелле (в стандартном Prelude) нет аналога partial_ordering.

Почему же нет, есть. Надо просто определить обе функции, а не минимальный набор

Нельзя. Ord — это класс линейно упорядоченных типов и реализация обязана быть консистентной и не нарушать соответствующих свойств, в частности forall a b. (Ord a, Ord b) => a <= b || b <= a. Для NaN соответствующее свойство нарушается, и из-за этого, например, алгоритм сортировки списка выдаёт непредсказуемый результат на списке, содержащем NaN.

Не очень понятно зачем держать в списке NaN, код построеный на таких допущениях кривой уже на уровне архитектуры. Это надо фиксить не на уровне языка. А есть другие примеры кроме NaN? Иначе это может трактоваться как новый способ отстрела ног, то есть теперь вместо сортировки нога отстрелится на попытке деления или чего подобного.

Да почему же только из Хаскелля. В том же Rust, насколько я помню, чуть ли не с самого начала есть трейты PartialEq/Eq и PartialOrd/Ord… Правда, никакого аналога weak_ordering я там не помню (если вернул Ordering::Equal, значит, значения равны), это да.

Наш код можно ещё упростить, если категорию сравнения определять автоматически:

И какая же категория будет выведена автоматически?

в зависимости от возвращаемого значения operator <=> для полей структуры/класса

Невероятно, но это тот самый случай, когда я начинал читать статью с мыслями "не надо трогать мой любимый С++, и без космических кораблей жилось хорошо", а закончил с мыслями "как я раньше без этого жил" :)


Интересно, что про spaceship говорили еще после С++14 вроде, но тогда по прочтении я еще не пробовал функциональные языки вроде Haskell или даже Rust. Теперь понять смысл почему это так удобно намного проще.


Кажется и правда функциональщина мозги вправляет) Или просто шишек себе уже набил за эти годы обо все эти операторы)

А в чом проблема, господа?
Кто писал операторы <, >, = так и дальше смогут их писать, для них ничего не изменилось.
А единый оператор <=> позволяет компилятору вывести все остальные автоматически.
— Я никогда не писал "<=>"
— Ладно, не заливай! Ни разу не писал оператор трехстороннего сравнения?!
— Не довелось как-то, не вышло…
— Ну ты даешь! Не знал, что на конференциях и даже на хабре никуда без этого?! Пойми. Там только и говорят что про оператор "<=>". Как он бесконечно прекрасен. О коде, который они видели. О том, как код, словно по волшебству, становится короче, делая то же самое, что стало легче сравнивать вещественные числа и строки, без учёта регистра. А ты? Что ты им скажешь? Там тебя окрестят лохом!..
большинство операторов сравнения в стандартной библиотеке заменили на <=>
Классический пример — числа с плавающей запятой, для которых любая из операций 1.f < NaN, 1.f == NaN и 1.f > NaN даёт ложь. Поэтому 1.f <= NaN также даёт ложь, но при этом !(NaN < 1.f) — правда.

Мы ж на Хабре, и вроде как не для 1С-ников пишем, зачем true и false переводить-то?..

Итак, у нас есть строка.
Раньше я реализовывал оператор скажем < следующим образом:
if (a.length()<b.length())
return true;
… а здесь более сложная логика, когда все таки длины у них одинаковые и надо уже идти проверять что там внутри.

Таким образом оператор < в подавляющем количестве вызовов был очень быстрым, т.к. тупо сводился к проверке длины.

Теперь мне говорят — для упрощения кода, ты можешь определить spaceship и
ВСЕГДА будет вызываться условный compare при сравнении.
То есть оператор < автоматически сегенерируется как:
return a.compare(b) < 0;

Эффективный код? Забудьте. compare(spaceship) сделает все возможные проверки, чтобы точно рассказать о том, какие отношения у двух переменных. Даже если вам это не надо.
Но ведь никто не запрещает определить оператор < и не оставлять его генератору!!!
Ага. Поэтому люди которые рассказывают какой классный spaceship — почему-то приводят в пример как раз строки и другие сложные структуры.
Как плюс выставляя тот факт, что теперь не надо писать сложные проверки а можно все делать через один оператор. То есть даже сами эксперты умалчивают о проблеме избыточности оператора, даже там где это актуально.
Второй момент — для кого нужен этот оператор?
Для тех, кто пишет много не типичных классов с переопределенными операторами. то есть 99% пользователей этой фичи — писатели библиотек. И уж они то будут пользоваться по полной, чтобы оптимизировать время написания кода.
Поздравляю, с внедрением spaceship библиотеки которыми вы пользуетесь станут еще медленней.

UPD: для минусующих карму.
Каждый раз, когда я высказываюсь с критикой изменений в С++ мне приходят минусовать карму.
Господа минусующие — вы идиоты. Чтобы указать на неверность критики — надо минусовать комментарий и комментировать контраргументами. Это видно и это показывает ваше отношение к конкретному высказыванию. Минусуя карму вы просто затыкаете критика.
Критика(даже глупая) не вредит стандарту, потому что специалист видит несостоятельность утверждения и игонрирует критику. А полезная критика заставляет задуматься и изменить что-то если надо.
Минусуя карму в такой ситуации вы просто увеличиваете вероятность, что проблема не будет замечена, потому что тот кто ей видит(или считает что видит) — предпочтет промолчать, зная что придут фантики которые его будут сливать.

Так и в spaceship можно точно так же сразу первой строчкой проверить length и сразу тут же и вернуть XXX_ordering::less, почему обязательно "все возможные проверки" — то?

Вы цепляетесь к деталям, игнорируя общий посыл.
Давайте чуть более абстрактно.
Есть две дорогих проверки первая проверка
A > B
вторая проверка
A < B
Если я делаю отдельные операции > и <, то каждая из них будет содержать только одну дорогую проверку, нужную в конкретном случае.
Если же я буду делать через spaceship — не получится для обеих операторов сделать дешево.
Либо при сравнении A > B будет сначала идти ненужное
if (A< B) return XXX_ordering::less;
а только потом сработающее
if (A > B) return XXX_ordering::greater;
либо наоборот.

Суть в том, что spaceship не знает и не может знать, какие проверки нам нужно делать в конкретном случае, а какие нет. Это общая большая функция вычисляющая отношение между переменные в деталях, даже если нам эти детали не нужны.
Разговоры «в общем» — это, конечно, здорово, но малоконструктивно :) Обычно пишут что-нибудь вроде «вот конкретный код с operator <, а вот эквивалентный с operator <=>, я считаю, что он будет работать медленнее в таких-то и таких-то случаях потому-то и потому-то». Желательно еще сделать тесты, что это замедление действительно заметно на реальных задачах, скажем, при сортировке, поиске и так далее. Вот на КОНКРЕТНЫЙ пример с предварительным сравнением length я и ответил, а лить воду «в общем» — увольте.

P.S. в общем-то и до spaceship'а обычно реализовывали всего два оператора — "==" и "<", а все остальные выражали через них путем вызова этих двух.

P.P.S.

if (A< B) return XXX_ordering::less;
if (A > B) return XXX_ordering::greater;

Почему не return A <=> B?

Внутри operator <=> тоже можно и нужно сравнивать члены структуры или класса в свою очередь через их operator <=>. Ну, это так, детали.

Для сравнения часто недостаточно просто взять переменные и их сравнить.
Сначала нужно провести определенные вычисления.
Ок, я пока помолчу и сделаю пример, раз вы так настойчиво не воспринимаете идею на абстрактных примерах.

Да, это было бы хорошо, чтобы, так сказать, в цифрах вы подкрепили как-то свои опасения. Я думаю, что если бы в std из-за этой замены будет реальная регрессия в плане производительности, это будет замечено на тестах.

В std не будет регрессии, там уже все операторы друг через друга выведены.
Ну и в std простые типы, там не требуется делать никаких вычислений для того чтобы провести сравнение.

Ну да, и я к тому же. У вас же были опасения что вот мол писатели библиотек будут это использовать и будет медленно, std — это библиотека, и вряд ли там будет какая-то регрессия из-за применения этого оператора. А в сложных ситуациях можно написать каждый оператор отдельно, это уже up to автор библиотеки. Если у автора затруднения с написанием эффективного кода, то operator <=> — не единственное место, где это проявится :) В общем, я думаю, что проблема несколько надуманна.

Либо при сравнении A > B будет сначала идти ненужное
if (A< B) return XXX_ordering::less;
а только потом сработающее
if (A > B) return XXX_ordering::greater;
либо наоборот.

А вот это заведомо плохой код. Вам нужно вызывать оператор A <=> B.
Суть оператора spaceship — не в оптимизации скорости выполнения, а в значительном сокращении количества кода.

Точно так же. Предполагается, что этот оператор существует для всех типов.

Сравнение сложных типов далеко не всегда сводится к сравнению их полей.
Часто нужно делать доп вычисления, например.

Ну так ничто не мешает использовать новый оператор в дополнительных вычислениях. Мне сложно представить пример, где этот оператор приводил бы к проблемам.


Приводимый вами пример для строчек — плохой, потому что он прекрасно решается новым оператором без каких-либо проблем.

C++ уже давно стал языком, который не особо жизнеспособен без оптимизирующего компилятора. Так что никаких проблем с производительностью не будет.

Крутая оптимизация С++ это спорная штука, рассчитывать на неё не самое правильное решение.
Ну и в целом подход «пишу как хочу, компилятор сам всё сделает» не работает на практике.

Если не рассчитываете на оптимизацию, то пишите на C, а не на C++.

А разве нет? Правда, бойлерплейта больше, а так да, завезли)
P.S. Кстати, я как-то сначала пропустил начальную фразу «итак, у нас есть строка». То есть вы при сравнении строк сразу считали, что одна строка «меньше» другой, если ее длина меньше? То есть по вашему алгоритму сравнения, скажем, строка «B» должна идти впереди строки «AA»? Но почему? Ведь должно быть ровно наоборот. Или я что-то не понял в сути вашего алгоритма?
Семантика сравнения срок контекстозависимая.
Например, стандартное сравнение считает что «15» < «2». В зависимости от задачи это может быть как приемлемо, так и не приемлемо. Поэтому обсуждать абстрактное сравнение срок в отрыве от конкретной задачи достаточно бессмысленное дело.
Ну так-то обычно все-таки считается, что идет сначала «AA», а потом «B». Впрочем, я не спорю, что если нет задачи представления отсортированного списка человеку в, так сказать, «привычном виде», то алгоритм может быть какой угодно, лишь бы работал по понятным и однозначным правилам.

Удобней же структуры сравнивать по другому, по очереди для всех полей проверяешь что они не равны, и если это так возвращаешь первый меньше второго, ну и с последним так же. Получается n сравнений в худшем случае, вместо 2*n-1, более того они могут быть легковеснее, так как сравнение на неравенство может быть реализовано эффективнее сравнения на меньше.

Можете пояснить мысль? Идея оператора <=> возникла не на пустом месте. Процессору нужна всего одна инструкция, чтобы вычислить все 6 операций сравнения для целых чисел.

Всё так, моя идея относится к коду до С++20, а более оптимальный код может получиться в дебажной сборке или например лексографическом компараторе.
Вместо:
bool operator< (A const& rhs) const {
if (t < rhs.t) return true;
if (rhs.t < t) return false;
return u < rhs.u;
}

Использую:
bool operator< (A const& rhs) const {
if (t != rhs.t) return t < rhs.t;
return u < rhs.u;
}

Мой вариант может быстрее если t и rhs.t лексикографические строки разной длины, тогда если для сравнения их на меньше нужно пробежать до первого расхождения, а в сравнении на неравенство — вначале стоит проверка на длину.
Чисто теоретически компилятор может все понять и соптимизировать до эквивалентного кода — но на практике это не работает: код.
Спейсшип должен решить эту проблему.
Мой вариант может быстрее если t и rhs.t лексикографические строки разной длины

но медленнее при равной длине. Собственно, для обхода этой несуразицы и был придуман std::string::compare
На удивление у clang-а при использовании spaceship-а сейчас ассемблер выглядит значительно хуже — 8 сравнений, лишние сдвиги и арифметика.

Он вообще какую-то дичь выдал, вроде:


  cmp edx, ecx
  setae al
  cmp edx, ecx

Ну не преувеличивайте.. в C++17 чтобы сравнивать без учета регистра достаточно создать кастомный std::char_traits

Sign up to leave a comment.