LINUX.ORG.RU

Развлекательная теория и практика для C++ программистов

 , , ,


3

3

Приветствую C++ программистов. Вы серьёзны и суровы. Но уверен, что развлечения и юмор вам не чужды. Поэтому сегодня у меня для вас ссылки на необычные ресурсы.

Во-первых, я написал статью наоборот. Я всегда писал, как сделать C++ код лучше. В этот раз я перешёл на тёмную сторону. Предлагаю вашему вниманию "50 вредных советов для С++ программиста". Будьте ментально аккуратны. Там зло. Если что - я вас предупреждал :).

К сожалению, некоторые программисты не согласятся, что все советы в той или иной степени вредные. Поэтому я заранее написал пояснения к наиболее неоднозначным пунктам. Думаю, у каждого найдётся коллега, кому будет полезно всё это почитать :).

Приятного чтения!

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

Удачи: Челлендж от анализатора PVS-Studio: насколько вы внимательны?

К сожалению, всё равно иногда есть неоднозначность, куда же нажать. Сделать задания идеально однозначными не получается. Прошу сильно не заморачиваться и подойти к этому именно как к развлечению, а не экзамену.

Ответ на: комментарий от kvpfs

fn(E(30)); // Не сложнее !

Да уж.

Так заморачиваться над ливером - игра свечь не стоит, скорее ещё должен останешься.

Да уж два раза.

Ладно, с таким уровнем ыкспертизы лучше и не спорить.

eao197 ★★★★★
()
Ответ на: комментарий от AntonI

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

firkax ★★★★★
()

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

hobbit ★★★★★
()
Ответ на: комментарий от eao197

Ладно, с таким уровнем ыкспертизы лучше и не спорить.

Ну тогда хотелось бы посмотреть как передают битовые патерны настоящие эксперты, может я научусь чему. Можно пример? Передаём одно из или комбинацию {one, two, three}. Это не интерфейс, а ливерная часть проекта.

kvpfs ★★
()
Ответ на: комментарий от firkax

Я прочитал. Вот @eao197 (к которому я отношусь с большим почтением) считает, что не надо использовать знаковые целые там, где значение не может быть отрицательным.

Но другие, не менее уважаемые мной люди (настоящие программисты, не то что я) напротив говорят что там где счетчик цикла лезет в int - надо юзать int из соображений производительности. Даже если счетчик больше или равен нулю.

По мне так это бодание остроконечников с тупоконечниками. Да, unsigned имеет вдвое большую емкость - но если ты в signed не влез, то лучше взять таки размер побольше, 2Гб от 4Гб отличаются слабо.

Из последних косяков о которых я стукнулся - в онтопике long 8 байт (и всякие fseek/ftell принимают/возвращают long), в оффтопике long 4 байта а unsigned long 8 байт. Но это косяк дизайна ИМНО, причем оффтопичьего.

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 2)
Ответ на: комментарий от kvpfs

Ну тогда хотелось бы посмотреть как передают битовые патерны настоящие эксперты

Так это нужно у настоящий экспертов спрашивать. А я не из этих.

Это не интерфейс, а ливерная часть проекта.

Прежде всего, не должно быть различия на интерфейсную и ливерную части. Поинт был в этом.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Ну или std::bitset

А неудобно же. Вот как раз тот самый компактный и традиционный пример выше будучи переделанным на битсетах превращается в тыкву размером с карету.

thesis ★★★★★
()
Ответ на: комментарий от AntonI

По мне так это бодание остроконечников с тупоконечниками.

На самом деле нет, т.к. есть вполне себе проверяемые вещи, вроде

«где счетчик цикла лезет в int - надо юзать int из соображений производительности»

это же все проверяется.

Так что можно начинать с того, чтобы использовать unsigned там, где знака быть не должно в принципе. А затем, если производительности не хватает и есть показания профайлера, то можно и на int перейти, если уж нужно каждый такт считать.

Только тогда в коде нужно пометить почему же int.

eao197 ★★★★★
()
Ответ на: комментарий от AntonI

По мне так это бодание остроконечников с тупоконечниками. Да, unsigned имеет вдвое большую емкость - но если ты в signed не влез, то лучше взять таки размер побольше, 2Гб от 4Гб отличаются слабо.

Дело не в ёмкости, а в том что в signed надо больше проверок корректности, по сути тавтологических. Замусоривание кода, как исходного так и бинарного. Хотя и в ёмкости тоже, если речь про size_t vs ssize_t. Первого гарантированно хватает, второго - нет.

в оффтопике long 4 байта а unsigned long 8 байт.

Не верится что-то в такое. Это грубое нарушение любых вариаций стандартов было бы, и вряд ли прошло бы незамеченным.

надо юзать int из соображений производительности

Подробности есть на этот счёт? Вот что (i<j) для signed будет считаться быстрее чем для unsigned.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от thesis

Вот как раз тот самый компактный и традиционный пример выше будучи переделанным на битсетах превращается в тыкву размером с карету.

Перила на лестницах и изоляция на проводах обходятся отнюдь не дешево. Но люди почему-то на такие траты идут.

Тогда как уж сколько людей наступали на грабли с Сишным enum-ом и беспрепятственным и незаметным преобразованием из enum в int и обратно, но все равно ничему учиться не хотят.

eao197 ★★★★★
()
Ответ на: комментарий от firkax

в signed надо больше проверок корректности

for(int i=0; i<sz; i++){ ... }

где здесь больше проверок?

в оффтопике long 4 байта а unsigned long 8 байт.

Не верится что-то в такое.

https://docs.microsoft.com/ru-ru/cpp/cpp/data-type-ranges?view=msvc-160&viewFallbackFrom=vs-2019

Да, прошу прощения, unsigned long тоже 4 байта. Не туда посмотрел видимо в ночи.

AntonI ★★★★★
()
Ответ на: комментарий от AntonI

где здесь больше проверок?

Тут - нигде. А вот хотя бы в условном

int set_value(signed index, void* value) {
  if(index<0 || index>=arr_size) return -1;
  arr[index] = value; return 0;
}

при замене index на unsigned проверку index<0 можно будет убрать, больше ничего не меняя - то есть она была чисто мусорным артефактом неверно выбранного типа для индекса.

firkax ★★★★★
()
Ответ на: комментарий от eao197

но все равно ничему учиться не хотят

Да я хочу. Не знаю, может это я ниасилил битсет, но юзать его и/или enum class для натыкивания флагов это какая-то необычайно острая жопная боль.

thesis ★★★★★
()
Ответ на: комментарий от firkax

Можно убрать. Но наверное тот кто заюзал там signed что то такое имел ввиду? Скажем ftell long не просто так возвращает, отрицательный результат там означает ошибку.

AntonI ★★★★★
()
Ответ на: комментарий от AntonI

Нет, в большинстве случаев в таких местах signed стоит только потому, что оно «int» из трёх букв, которое проще всего написать, не задумываясь о последствиях.

Скажем ftell long не просто так возвращает, отрицательный результат там означает ошибку.

Это было весьма сомнительное решение. Как и знаковый off_t позже (да, кстати, ftell если что по сути deprecated, надо ftello использовать, но это так, к теме не относится). Я бы сказал, тут первонисточник проблемы в том, что данные и код ошибки засунули в одну переменную, не дав никаких явных способов их отличить.

Аналогичная проблема имеется с read()/write() - на 32-битной платформе будет UB, если попытаться записать разом в файл больше 2гб (что вполне легитно само по себе, но приходится костылить нарезку объёма на куски меньше ssize_t).

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

Если так сильно беспокоит отрицательные значения, то тупо ставишь бесплатный ассерт:

int set_value(signed index, void* value) {
  assert(index >= 0);
  if(index>=arr_size) return -1;
  arr[index] = value; return 0;
}

Или отладочная версия и так упадёт при прогоне с санитайзером.

kvpfs ★★
()
Ответ на: комментарий от kvpfs

Это вообще не в тему. Ассерт либо не бесплатный, либо не работающий. И речь была не про падение а про возврат ошибки из какого-то апи.

firkax ★★★★★
()
Ответ на: комментарий от thesis

Не знаю, может это я ниасилил битсет

Я сам его не использую, т.к. из коробки в нем нет способа отличить один набор флагов от другого. Т.е. если у нас есть:

using formatting_flags_t = std::bitset<8>;
using horizontal_alignment_flags_t = std::bitset<8>;

void text::apply_new_style(formating_flags_t fmt, horizontal_alignment_flags_t halign);

То ничего не помешает по ошибке засунуть alignment вместо formatting. Тут нужно еще и каким-то тегом все это дело сопровождать.

Но в одном из подобных споров (возможно, на Хабре) мне указали, что std::bitset все-таки забывать не нужно, т.к. для управления битовыми флагами он может быть удобен. Так что вынужден упоминать.

Сам бы предпочел использовать не std::bitset, а какую-то из реализаций strong typedef (типа вот этой). Ну или сделал бы шаблонную обертку вокруг std::bitset, которую можно было бы типом-тегом параметризовать.

eao197 ★★★★★
()
Ответ на: комментарий от firkax

использование int (знакового, что школьников бесит) при обращении к векторам и в циклах, обучсловленно:

  1. код возможно унаследован

  2. заранее предусматривались фичи

1+2 - где-то индекс >=0 отсчитыается от условной головы вектора/массива, <0 от его хвоста. Это чертовски удобно в прикладном уровне.

  1. или в муравейнике так принято. Это самая веская причина. Твой напарник автоматом поставит hook/break/assert на отрицательную величину. Это тоже один из способов минимизации багов - «если величина меньше 0, значит баг в ней».
MKuznetsov ★★★★★
()
Ответ на: комментарий от AntonI

там где счетчик цикла лезет в int - надо юзать int из соображений производительности

Счетчик цикла лучше не иметь, а пользоваться парой указателей/итераторов.

Бывают случаи, когда нужно просто сделать действие n раз, без привязки к номеру итерации, и тогда да – но такие случаи совсем уж редкие.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Счетчик цикла лучше не иметь

Насколько я помню специфику вычислительной математики, там без счетчиков циклов (которые одновременно и индексы в массивах/векторах) совсем редко можно обойтись.

eao197 ★★★★★
()
Ответ на: комментарий от Siborgium

У меня регулярно возникает необходимость пройти два массива одинаковой длины и че то с ними вместе поделать. Иногда при этом надо знать номер итерации ко всему

AntonI ★★★★★
()
Ответ на: комментарий от unC0Rr

ХЗ, нет привычки наследоваться от классов из stdlib.

eao197 ★★★★★
()
Ответ на: комментарий от MKuznetsov

Странные аргументы.

1) легаси это понятно, но никак не пример для подражания

2) на скрипто-прикладном уровне, возможно, и удобно, поэтому в скриптовых языках часто нет unsigned, а вот на уровне системного программирования, для которого предназначен Си, это плохая практика; впрочем да, поскольку тема про С++, возможно это более значимый аргумент

3) проверку надо ставить не на отрицательную величину, а на превышение лимита; беззнаковое переполнение в минус тоже его превысит

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от AntonI

У меня регулярно возникает необходимость пройти два массива одинаковой длины и че то с ними вместе поделать

for (type_t * p = ..., * q = ...; p != end; ++p, ++q) {
    // ...
}

Иногда при этом надо знать номер итерации ко всему

Тогда можно и третью переменную завести.

Все это, конечно, имеет смысл только при относительно малом теле цикла, когда p, q, i не успеют «пролиться» из регистров. Но в противном случае и разница int vs uint значения практически не имеет.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от AntonI

но деталей я не помню.

Идея очень простая: переполнение int - UB, посему «не случается», что даёт компилятору бОльший простор для оптимизаций. Классический пример - лупчик с умножением в теле которое трансформируется в сложение. Да и вообще - если значения «дрожат» возле нуля оставаться в signed в общем случае безопасней. Там у меня в параллельной ветке вылез лупчик с int который оказался медленнее чем unsigned - будем разбираться, если понадобится с привлечением «тяжелой артиллерии» в виде разрабов gcc так как непосредственно влияет на company coding guidelines.

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

Этот вариант конечно лучше с точки зрения скомпилированного кода, но исходник стал выглядеть ещё хуже. Зачем костыльно кастовать к unsigned, когда можно сразу сделать unsigned?

firkax ★★★★★
()
Ответ на: комментарий от bugfixer

Идея очень простая: переполнение int - UB, посему «не случается», что даёт компилятору бОльший простор для оптимизаций. Классический пример - лупчик с умножением в теле которое трансформируется в сложение.

Выглядит теоретизированием. Практический пример конкретного кода есть? Хорошего кода, а не так что компилятор исправляет идиотизм кодера.

И кстати

переполнение int - UB

это режим -fstrict-overflow и в нём с signed-ами ещё больше проблем чем в обычном. Потому что переполнение, когда оно таки случится, возможности гарантированно поймать нет вообще, и надо костыльно-ручные проверки диапазона заранее везде ставить в итоге. Да, в ряде случаев диапазон действительно имеется и можно его проверить, но во многих его приходится именно придумывать ради проверок.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 3)
Ответ на: комментарий от eao197

какую-то из реализаций strong typedef (типа вот этой).

Ой мама. Эта тыква размером даже не с карету...

То ничего не помешает по ошибке засунуть alignment вместо formatting.

Вообще, конечно, интересно наблюдать, как люди пытаются предохраниться от ошибки типа «в функцию, получающую int, можно заслать НЕ ТОТ int! ОПАСНОСТЕ!!»

Но если серьезно, меня огорчает, что недостатки enum вроде как все обговорили, а альтернатива (для напихивания флагов) - enum class + bitset - требует оскорбительно долгой женитьбы вручную.

thesis ★★★★★
()

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

Assembler
()
Ответ на: комментарий от firkax

Этот вариант конечно лучше с точки зрения скомпилированного кода

А разве что-то ещё имеет значение в этом мире? ;)

но исходник стал выглядеть ещё хуже

Мнения разнятся.

Зачем костыльно кастовать к unsigned, когда можно сразу сделать unsigned?

Ну, мало ли почему народ может хотеть signed в интерфейсе. Это может означать что-то гораздо больше чем invalid index / index out of bounds. Например: «-1»: «найди первый свободный слот в начале», «-2»: «найди первый свободный слот в конце», итд. И если оно случается «раз в пятилетку» - я за один conditional jump изолировал hotpath (если это действительно hotpath я ещё и likely / unlikely обмазываться буду). Ну, и качестве дополнительных «плюшек» - специальные значения «не съезжают» при конвертации 32 <-> 64, причём практически «бесплатно» (если в домене signed integers оставаться конечно).

bugfixer ★★★★★
()
Ответ на: комментарий от firkax

Выглядит теоретизированием.

Если бы.

Практический пример конкретного кода есть?

И да, и нет. Reduce’нуть что-то из нашего у меня пока нет времени, а выкладывать as-is я по объективным причинам не могу. Позже (сейчас небольшой аврал по нескольким проектам - не до того). Да и вообще - «тёплыми летними вечерами» после «тяжёлых трудо-выебудней» хочется сидеть на верандочке с винишком, а не доказывать кому-то что-то сидя за компом «в чатиках»… Старенький я ужо для всего вот этого вот…

это режим -fstrict-overflow

Что есть default. Потому как это то что говорит стандарт.

и в нём с signed-ами ещё больше проблем чем в обычном

Мнения снова разнятся.

Потому что переполнение, когда оно таки случится, возможности гарантированно поймать нет вообще

Assertions and unit tests «наше всё».

Да, в ряде случаев диапазон действительно имеется

В моей практике - в подавляющем большинстве случаев. Я уверен - найдутся другие мнения.

bugfixer ★★★★★
()
Последнее исправление: bugfixer (всего исправлений: 2)
Ответ на: комментарий от thesis

Ой мама. Эта тыква размером даже не с карету…

Вас, наверное, смущает и объем реализации таких классов, как std::unique_ptr, std::shared_ptr, std::optional, std::tuple, std::variant (или их аналогов из Boost-а).

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

Вообще, конечно, интересно наблюдать, как люди пытаются предохраниться от ошибки типа «в функцию, получающую int, можно заслать НЕ ТОТ int! ОПАСНОСТЕ!!»

Такие дурацкие ошибки затем очень дорого искать.

Но если серьезно, меня огорчает, что недостатки enum вроде как все обговорили, а альтернатива (для напихивания флагов) - enum class + bitset - требует оскорбительно долгой женитьбы вручную.

Сделайте один раз свой шаблонный класс strong_typedef<T,Tag> с собственными operator| и operator& и всех делов.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Нет, std меня не смущает, потому что подразумевается, что это знают и ожидают увидеть в проекте абсолютно все. А вот тянуть сторонний код ради настолько, казалось бы, примитивной задачи, которую еще деды 50 лет назад решили и забыли (я про передачу флагов)...

Сделайте один раз свой шаблонный класс strong_typedef<T,Tag> с собственными operator| и operator& и всех делов.

А потом окажется, что, раз уж пошла такая пьянка, то в сто раз проще забыть про существование шаблонов, перегрузку операторов, strong typedef, bitset с наследованием от него и enum class вообще, и сделать struct{bool FLAG_A, FLAG_B, ... }. И потомки скажут спасибо.
Вот у меня сто лет назад был курсач по крестам, там прямым текстом было оговорено задание напихать туда максимум известных крестовых фич. Но у нас же такой цели нет, правильно?
Но вообще я прошу прощения за назойливость, просто я, влезая в разговоры о крестах, надеюсь, что меня ткнут носом где я дурак и научат более правильно мыслить, а то закостенел я чот.

thesis ★★★★★
()
Последнее исправление: thesis (всего исправлений: 2)
Ответ на: комментарий от thesis

А вот тянуть сторонний код

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

Да полноте. Даже в проекте на 50KLOC будет дофига того, что сторонний человек, не погруженный в проект, не знает и с чем обязательно придется знакомиться.

ради настолько, казалось бы, примитивной задачи, которую еще деды 50 лет назад решили и забыли (я про передачу флагов)…

Деды ее решили так, что ошибиться с передачей не того не туда как два байта…

сделать struct{bool FLAG_A, FLAG_B, … }. И потомки скажут спасибо.

Для меня показательной оказалась ситуация, в которой команда из 6 человек часа четыре в авральном режиме искала баг. Который заключался в том, что где-то в коде кто-то перепутал username и password при вызове метода db.connect потому, что оба параметра были простыми string-ами.

Нехило так, не правда ли?

Дело было в Java, но там и возможностей сделать бесплатный strong-typedef по тем временам и не было вообще, насколько я помню.

А вот в C++ можно, причем уже лет 30 как. И это реально спасает. Не только для флагов. Но даже для последовательно идущих аргументов одинакового типа в функциях/методах. Вроде функции, которая получает 4 int-а или 4 bool-а подряд.

Многие программисты о таких вещах даже не задумываются. Хотя выходов (причем простых и дешевых) можно сходу две-три штуки назвать.

Но нет, давайте поминать дедов и надеяться, что потомки скажут спасибо.

Не скажут. Проверено на себе.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Вы хотите сказать, что в более-менее серьезном проекте будет только stdlib и все?

У меня есть граничащее с религиозным стремление тащить в проект как можно меньше постороннего. Все обязаны ориентироваться в stdlib, но никто не обязан ориентироваться даже в Qt, boost и чем угодно сколько угодно популярном. Любая васянская либа это потенциально скорый крик джуна «кто втащил в проект это говно? Как оно вообще работает?? Дайте я перепишу!!», независимо от чоткости васяна и либы.

где-то в коде кто-то перепутал username и password

Вот здесь непонятно. Хрен с булями, допустим в моем примере не були, а строки.

db_connect({.username="user", .password="1234"});

Как именно здесь можно что-то перепутать? Это ж даже любой вебезьяне знакомо.

надеяться, что потомки скажут спасибо.

Ну хорошо. Пусть не скажут, но почему именно?

thesis ★★★★★
()
Последнее исправление: thesis (всего исправлений: 1)
Ответ на: комментарий от thesis

У меня есть граничащее с религиозным стремление тащить в проект как можно меньше постороннего.

И что, вы откажитесь затащить Asio и будете работу с socket-ами вручную делать (особенно если вам не нужна супер-пупер производительность)? Или логирование сами напишете, а не spdlog примените?

юбая васянская либа это потенциально скорый крик джуна

Так на то они и джуны, чтобы их не слушать. Пусть кричат сколько хотят.

db_connect({.username=«user», .password=«1234»});

Во-первых, например, в C++17 вы так не напишите. Будет именно что

db.connect("user", "1234");

Или

make_rect(215, 526);

Для того, чтобы преобразовать это в что-то вроде:

db.connect(username{"user"}, password{"1234"});
...
make_rect(width{215}, height{526});

уже нужно отойти от привычных для многих подходов.

Пусть не скажут, но почему именно?

Потому что наступят на грабли, оставленные дедами. Причем наступят неоднократно. Т.к. дедам привычнее было писать:

void connect(const string & user, const string & password);
...
void make_rect(int width, int height);
eao197 ★★★★★
()
Ответ на: комментарий от eao197

Мой колхозный вариант битовых флагов

#include <cstdint> 

#pragma push_macro("Self")
#undef Self
#define Self MyFlagsOption 

struct Self {
	union {
		struct {
			bool flagA: 1;
			bool flagB: 1;
			bool flagC: 1;
		};
		uint_least8_t rawData = 0;
	};
};

Self operator| (const Self& x, const Self& y) {
	return { .rawData = uint_least8_t(x.rawData | y.rawData) };
}
Self operator& (const Self& x, const Self& y) {
	return { .rawData = uint_least8_t(x.rawData & y.rawData) };
}

#pragma pop_macro("Self")

void doAction(MyFlagsOption opt) {
	
}

int main() {
	MyFlagsOption opt1 = { .flagA = 1 };
	MyFlagsOption opt2 = { .flagB = 1 };
	
	doAction(opt1 | opt2);
	doAction({ .flagA = 1 });
	doAction(MyFlagsOption({ .flagA = 1 }) | opt2);
	
	return 0;
}
bga_ ★★★★
()
Ответ на: комментарий от eao197

нужно отойти от привычных для многих подходов.

Так я ж не возражаю против отхода! Просто мой подход мне видится более щадящим, что ли. Причем и для компилятора, и для человека.

thesis ★★★★★
()
Ответ на: комментарий от bga_

Мой колхозный вариант битовых флагов

Я не перешел еще на C++20, но вроде как в C++17 (и предшествующих стандартах) у вас тут UB, т.к. изменение flagA не начинает время жизни для rawData в вашем union-е Self.

О том, как оно в C++20, емнип, можно прочитать здесь: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0593r6.html

eao197 ★★★★★
()
Ответ на: комментарий от bga_

А зачем operator&? Дань традициям, или с ним реально удобнее?

thesis ★★★★★
()
Последнее исправление: thesis (всего исправлений: 3)
Ответ на: комментарий от thesis

Просто мой подход мне видится более щадящим, что ли.

Подход выбирается исходя из того, что нужно.

Например, чтобы не путать «длину» с «высотой» может быть достаточно всего лишь:

struct width{ int v_; };
struct height { int v_; };

Чтобы не путаться в bool-ах достаточно использовать enum class.

А вот если нам нужны значения, к которым можно применять конъюнкцию и/или дизъюнкцию, то уже потребуется что-то посерьезнее.

И, если мы начинаем такие трюки применять, что какая-то шаблонная реализация strong_typedef может здорово помочь, т.к. будет содержать в себе какой-то дополнительный функционал. Например, возможность вывода в std::ostream.

В качестве примера я привел реализацию из type_safe потому, что это одна из самых продвинутых и гибких, из виденных мной в прошлом.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Чтобы не путаться в bool-ах достаточно использовать enum class.

О том и речь, что enum class для этого не годится совершенно. Да вот и вы сам предпочли ему strong typedef. А я и _bga - ну, назовем это «более тупой typedef», причем мой тайпдеф самый тупой.

thesis ★★★★★
()
Последнее исправление: thesis (всего исправлений: 2)
Ответ на: комментарий от thesis

Чтобы не путаться в bool-ах достаточно использовать enum class.

О том и речь, что enum class для этого не годится совершенно.

Для замены bool-ов enum class не годится?

Да ладно вам. Скажем, было:

some_handle open_handle(const std::string & name, bool auto_init, bool auto_close);
...
auto my_handle = open_handle("...", true, false);

Элементарно переделывается в:

enum class auto_init { yes, no };
enum class auto_close { yes, no };

some_handle open_handle(const std::string & name, auto_init auto_init_mode, auto_close auto_close_mode);
...
auto my_handle = open_handle("...", auto_init::yes, auto_close::no);

Так что с последовательностью bool-ов enum class просто на ура справляется.

А вот со случаем битовых флагов – не очень. Хотя, тут как посмотреть: https://wandbox.org/permlink/tpyhgltWPUIH3yVi (а в C++23 обещают to_underlying завезти).

eao197 ★★★★★
()
Последнее исправление: eao197 (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.