Как стать автором
Обновить

Комментарии 77

Я вот знаю как минимум один повод вовсю использовать макросы в современных приложениях. Активно использовал в проектах, работающих с оборудованием по бинарным протоколам: Х-macro. Сложно переоценить, насколько эффективными они бывают, когда нужно описать последовательный набор похожих друг на друга последовательностей данных с различной обработкой их элементов. Объем кодогенерации реально спасает. Всем рекомендую.

EDIT: вот очень годные примеры использования.
Насколько же неэффективна отладка такого кода. И обучение дисциплине и сдержанности разработчиков тоже тратой времени становится. Рекуррентные шаблоны и вменяемая декомпозиция логики гораздо безопаснее.

К сожалению, X-macro не всегда можно адекватно заменить на шаблонный код. Классическое применение — функция-маппер вариантов перечисления в строку и обратно. А ещё перечисление может "внутри" мапиться на что-нибудь нетривиальное. Справедливости ради, это действительно один из немногих случаев, когда макросы реально полезны.

Отладку усложняют не только такие «устаревшие» вещи, но и вполне новомодные. Недавно получил в наследство проект (правда на C#, как с этим обстоит дело в С++ не в курсе) в котором изрядная часть кода — это лямбды (местами довольно большие). С т.з. отладчика лямда — это один оператор (если в дизассемблер не лезть), что очень «облегчает» их отладку.

Да, а что с отладкой шаблонного кода? Мало с ними сталкивался, так что интересно, что меня там ждёт…
Я думаю, у вас тут скорее проблема инструмента, чем языка. Вы и сами на это указали:
С т.з. отладчика лямда — это один оператор (если в дизассемблер не лезть), что очень «облегчает» их отладку.

P.S. В С++ (по крайней мере, в VS2017) не испытывал проблем отладки шаблонных классов
Вот как раз он лямбы отлаживать и не умеет. Интересно, а хоть один умеет? К вопросу об инструментах.
На поставленный breakpoint в тело кода лямбды не реагирует?
Да, на БП сработал. Я ступил. :(
А можете пояснить, с какими конкретно проблемами вы столкнулись при отладке C# кода с лямбдами? Правда интересно, потому что у меня не было никаких проблем с отладкой.
Проблема одна. Отладчик не хочет заходить в лямбду. Может просто руки кривые (я с ними раньше дел не имел). Тут подумал — может надо было использовать вход в функцию. Просто у меня вообще лямбды бессмысленно (imho) сделаны: вся функция состоит из одного return, в который запихана огромная лямбда, которая возвращает результат работы этой функции. Зачем так надо было делать — загадка.
Адаптер интерфейса?
Да вроде обычный класс. А в чём проблема для интерфейса сделать без лямбд?
Проблема решается выставлением галочки «Just my code» в свойствах отладчика. Тогда отладчик будет заходить внутрь пользовательских лямбд при вызове библиотечных функций.
Стоит (по умолчанию, похоже), запомню. А тут проблема была в кривых руках
В большинстве таких случаях удобней использовать внешнюю генерацию кода, а не сражаться с макросами C.
О да! Встроенного препроцессора мало — надо писать свой. Flex и Bison в помощь :)
Да, встроенного препоцессора мало. Он изначально задумывался, как «нечто простенькое, что есть всегда». И m4 использовался в качестве замены, когда его возможностей не хватало, а иногда и что-нибудь посерьёзнее. А потом… потом появились IDE. Которые встроенный процессор поддерживали, а всё остальное — нет. И тогда этой «тележке для грузчика» начали пользоваться для того, чтобы возить грузы между континентами. Она для этого приспособлена плохо, грузы мокнут и портятся, но… альтернативы-то «типа нет»!

Кажется последняя популярная библиотека, которая реализовывала всё примерно так, как это изначально было задумано — это Qt, где вместо того, чтобы насиловать препроцессор написали moc. Но это, в некотором смысле, «последний из могикан»: Qt писалась во времена, когда подход «библиотека должна быть совместима с моей любимой IDE, а иначе я её использовать не буду» ещё не стал доминирующим.

В современном же мире люди будут скорее насиловать мозги и PVS-Studio тонной макросов, чем подключат к проекту ещё один препроцессор (что, кстати, иронично, так как, в отличие от Turbo Pascal и Turbo C, современные IDE это позволяют сделать).
И Qt до сих пор прекрасна.

Вроде, еще лет 10 назад можно было везде свои команды сборки настраивать. Странно все это. Но люди ленивые, так что ожидаемо.
НЛО прилетело и опубликовало эту надпись здесь
Ну. Я не сталкивался. Видимо, не достаточно много пишу.

Я нашёл для себя неплохое правило. Все макросы именовать начиная с $. Частью идентификатора он быть не может, зато препроцессоры "большой тройки" воспринимают его как нормальный допустимый символ. Заодно убирает проблему конфликтов имён и для аргументов макросов.

Все макросы именовать начиная с $. Частью идентификатора он быть не может
Это кто вас так жестоко обманул? Идём по ссылке — а потом в магазин за губозакатывательной машинкой.

зато препроцессоры «большой тройки» воспринимают его как нормальный допустимый символ.
Не «зато». Стандарт действительно оставляет это на усмотрение разработчиков компилятора, но фишка тут вот в чём: если компилятор таки не считает доллар валидным символом, то и в препроцессоре он запрещён тоже. А если считает — так он, конечно, разрешён везде. Чтобы программы с VAX'ов, где он часто разработчиками на C использовался для имитации namespaceов было удобнее переносить. Так что применяем машинку ещё раз.

P.S. В MSVC в полном соответствии с документацией (скроллить до фразы The dollar sign $ is a valid identifier character in Visual C++) доллар разрешён всегда, в clang'е и gcc — да, это опция, которую можно включать и выключать, но, опять-таки, везде.

А где же полноценная проверка Миднайт коммандера?

Его без нас проверяют и правят :).

Ну я в свое время много косяков нашел используя cppcheck.
Но я не видел чтобы mc тестили ваши продуктом.

На сколько помню Apple отказалась от GCC из-за смены лицензии на GPLv3 после версии 4.2.1 а не из-за макросов в коде

У них было выбор — форкнуть или попробовать допилить LLVM. И есть подрзрение, что посмотрев на код GCC они таки решили допиливать…
Если известно, что входным значением всегда является константа, то можно добавить constexpr и быть уверенным, что все вычисления произойдут на этапе компиляции.
К сожалению быть уверенным можно только если описать функцию как consteval (C++20). Если у вас более старый диалект языка, то нужно результат работы constexpr-функции засунуть в constexpr-переменную — только тогда можно быть в чём-то уверенным.
Вспоминается движок UE4, там таких макросов просто море.
Могу добавить, что A2W и T2W еще хуже, чем кажутся.
Если их вызвать в функции, которая вызывается в цикле, а компилятор решит ее заинлайнить…
Ну вы поняли, что будет :)
Вообще, alloca — изощренный способ стать инвалидом.
А какие можно предложить альтернативы для написания конечного автомата для реализации кооперативной многозадачности в embedded?
Там обычно много boilerplate-кода вида:

switch (state)
{
  case 0: // начальное состояние
  // много буков
  state = __LINE__; return true; case __LINE__: // такое повторяется при каждой смене состояния или в контрольной точке
  // много буков
  default:
    return false;
}
НЛО прилетело и опубликовало эту надпись здесь
… и году к 30-му они станут доступными на нужных целевых платформах.
Это еще полбеды, когда используются только стандартные макросы… Приправим сюда еще m4, Qt-шный MOC и вот тогда наступает настоящий ад!
Кстати, настоящей замены #define то до сих пор нет.
constexpr int A = 5;
int f()
{
    auto B = &A;    
    return A;
}

A:
        .long   5

antoshkka я конечно не достаточно эксперт по C++ и стандартам его, но может можно расширить consteval:
consteval int A = 5;
//работает как #define A 5, только на уровне компилятора
int f()
{
    auto B = &A; //ошибка
    return A+1; //нормально
}
Объясните, пожалуйста, какую высокоуровневую задачу вы решаете. Конечно если вы возьмёте адрес переменной — то её придётся в объектник засунуть, как иначе? Но если адрес не брать — её и не будет.

Почему вам этого не хватает? Если ошибок боитесь — ну сделайте класс с приватным operator&
класс с приватным operator&…

Разве поможет?
int f(const int &a)
{
    return a;
}
constexpr int A = 5;
int main()
{
    return f(A);
}

A:
        .long   5
Поскольку вы так толком и не сказали с чем вы пытаетесь бороться, то я не могу сказать — поможет или нет. Заметьте, что ваша программа прекрасно компилируется, если заменить в ней constexpr int A = 5; на #define A 5. При использовании «охранительного» класса будет точно также создаваться временный объект и передаваться ссылка на него, а если попытаться взять адрес — то вас об этом вежливо известят.
И тут на помощь приходит std::addressof :D

Можно защититься через enum:
enum A { MyConstExprValue = 5} ;

auto x = &(A::MyConstExprValue);  // нельзя
auto x = std::addressof(A::MyConstExprValue);  // и так нельзя
И тут на помощь приходит std::addressof :DM
Что значит «приходит на помощь»? Основной принцип в C++, описанный ещё в изместной книжке — это то, что все защиты в C++ работают против случайного непреднамеренного доступа, но не против кражи или взлома.

Вы часто вообще используете std::addressof? Достаточно часто для того, чтобы могли случайно создать проблему (которую вы, кстати, до сих пор отказываетесь описывать)? Покажите — я хочу это видеть!
Ну я не то чтобы прямо борюсь, лично мне без этого жить можно. А цель в 1 сообщении — настоящая замена #define, где обсуждать такую тему, как не здесь.
Заметьте, что ваша программа прекрасно компилируется, если заменить в ней constexpr int A = 5; на #define A 5.
Поведение разное, #define в .rodata не попадает.

Ваши примеры не работают с -O0 или даже -Og, «константа» оказывается в .rodata, а с -O2 и static int A = 5; в данном случае будет выглядеть как константа.
Возможно тривиальное
consteval int A()
{
 return 5;
}
будет работать как #define эквивалент, потому что, судя по стандарту, взять адрес consteval функции принципиально нельзя, так что компиляторы возможно не будут генерировать тело функции даже с -O0.
Edit: да, походу можно с enum пошаманить.
Заметьте, что ваша программа прекрасно компилируется, если заменить в ней constexpr int A = 5; на #define A 5.
Поведение разное, #define в .rodata не попадает.
В каком разделе стандарта описана .rodata?

Edit: да, походу можно с enum пошаманить.
enum можно было для этого использовать ещё в C89.

судя по стандарту, взять адрес consteval функции принципиально нельзя, так что компиляторы возможно не будут генерировать тело функции даже с -O0.
Что, когда и как генерируется компиляторами — не определяется спецификацией языка. Можно себе представить компилятор, который будет и #define засовывать в .rodata. И уж тем более вас не должно волновать что он делает -Og: если вас не интересует скорость сгенерированого кода и потребляемая память — то есть масса других языков для этого.

А цель в 1 сообщении — настоящая замена #define, где обсуждать такую тему, как не здесь.
Извините, но ваша «настоящая замена» уже давно прератилась в настоящего шотладца.

Если не определить заранее — каким критериям предлагаемая замена должна удовлетворять — то не будет никакой возможности придумать замену.

Любая замена #define, разумеется, будет в чём-то от #define отличаться — а иначе какой смысл? Зачем в языке ещё одна сущность, которая полностью дублирует другую?
enum «настоящая замена», полностью подходит, чтот подзабыл я про такое использование.

На SO цитата на тему, в принципе именно то, что я хотел и имел в виду. «It's because enum never gets any storage while const variable is still a variable and will get (static) storage if the compiler can't proove it won't need one, which it often can't.»
Ответ, который вы там откопали дико стар — он был дан, когда ещё C++11 в компиляторах не было. Если компилятор не сможет во время компиляции выяснить значение constexpr-переменной — это ошибка компиляции. А то, что у такой переменной можно взять адрес — так это преимущество, не недостаток. Особенно в C++17, где constexpr-переменная — не static, а inline
из макросов сразу вспоминается
#define TRUE FALSE
#define PI 3.14268265359
#define struct union
… для экономии памяти.
И #define while if для быстродействия.
Важно, что макросы провоцируют ошибки.

Эээ, нет. Обе приведенные в пример ошибки провоцирует название макроса как одной из стандартных функций. Если макрос будет называться is_space_or_tab, то никому и в голову не придет ожидать от него поведения функции isspace.


Второе: усложняется чтение кода

Ну, тут как посмотреть. Или 20 строк, в которых 2-3 строки меняются, повторенных 40 раз, или 40 строк макровызова с четко видимыми аргументами. Макрос в этом случае еще и от опечаток страхует.


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

C++ вообще нужно использовать с повышенной аккуратностью :)

Если макрос будет называться/blockquote>
Я один использую редакторы отделяющие разной подсветкой функции и макросы?

В общем случае редактора маловато будет, IDE нужна, которая поймет, что в данном месте isspace — это функция из стандартной библиотеки, в месте 2 — функция из библиотеки в соседнем файле, а в месте 3 — макрос, определенный в дебрях системных библиотек, если стоит дефайн X и не стоит дефайн Y. Причем все 3 случая могут быть в одном файле

А если вместо макросов использовать шаблоны, то получаем ещё и бонусы от статической типизации и возможности полноценной отладки.
Если макрос нельзя отладить в голове и нужна его трассировка то это плохой макрос, я видел макросы с вложенными туда алгоритмами на 100 строк и более — искренне не понимаю людей пишущих такое. Тот случай когда алгоритм-алкоритм.
Написать «хороший» макрос не так и просто — сложнее чем эквивалентную функцию. Даже если переименовать isspace => is_space_or_tab все проблемы не уйдут. Например, при таком вызове:
is_space_or_tab(readCharFromFile());

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


Кроме того, некоторые вещи без макросов просто не реализовать — например, превращение токена в строку. Никакой шаблон или constexpr вам это не сделает. Особенно, если его нужно использовать и как строку, и как идентификатор. А очень часто если противник макросов — то не признает их ни в каком виде. При этом в описанной ситуации очевидно отказ от макроса делает только хуже.

А если бы isspace была бы функцией, это что, как-то кардинально бы поменяло ситуацию?

А разве в этом случае код бы собрался без проблем?
НЛО прилетело и опубликовало эту надпись здесь
Если бы он был в namespace? Да, легко.
А очень часто если противник макросов — то не признает их ни в каком виде.
А как такие умники код тестируют? GTest им не годится, значит… что? И как оно выглядит?

Мне кажется что разумное отношение — это некий «коэффициент толерантности»: поскольку макросы более опасны и их использование усложняет отладку, то их следует избегать… кроме тех случаев, когда они позволяют писать существенно меньше кода.

Что именно «существенно» — вопрос обсуждаемый, но если у меня в проекте человек, который готов вместо макроса на пять строк породить тысячу строк безумного кода на шаблонах… я, пожалуй, с ним работать не смогу.
Это тоже только «отмазка».


Это не правда. В спортивном программировании замена max и min на макросы позволяют немного ускорить код, превращая T/L в OK, если в задаче это критичные операции.
Это в каком году так было?
Во времена Turbo C наверное.
github.com/AIshutin/cpp-std-benchmark Сейчас (март 2019) в среднем -8% на GNU GCC 8.2. Тестил ДО снизу на больших (1e6 запросов) радномных тестах с операцией взятия максимума на отрезке и изменения в точке. Буду рад, если кто-нибудь потестит у себя локально и сравнит результаты.
А какое имеет отношение этот бенчмарк к замене std::max на макросы? Там, я извиняюсь, другой алгоритм, а не замена std::max на макросы.

Если же взять этот код, заменить std::max на стандартный макрос, то… та-да… скомпилированный код у двух вариантов будет идентичен до последнего бита!

Ни и откуда у идентичного кода ускорение на 8%? Может вы обладаете сильным чувством веры и умеете ускорять своей верой бинарники на 8% — но мне это, увы, не удаётся.

P.S. А вот вопрос на тему «а почему и как отказ от использования std::max (или макроса MAX) может ускорить ваш код» — это действительно хороший вопрос для собеседования. Но к «вере в макросы» он отношения не имеет.
1. Чем Вы их компилировали и с какими флагими, если они были? Я это делал без флагов.

2. У меня скомпилированный код из первого коммита разный

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

3. Я затупил и вместо define-а самостоятельно заменил max на if ручками (который Вы назвали другим алгоритмом. Странно, что у якобы разных алгоритмов якобы одиноковый скомпилированный код.) Это был некорректный бенчмарк не на ту тематику. Сейчас я написал нормальный нераскрытый макрос и у меня локально работает за примерно такое же время как и раскрытый макрос ранее

С -Ofast видимо оптимизируется до эквивалентого кода. У меня работает с таким флагом одинаково быстро.

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

P. S. Допустил багу во время написания коммента и исправил. Пост переписывал несколько раз.
1. Чем Вы их компилировали и с какими флагими, если они были?
По ссылке всё есть.

Я это делал без флагов.
Что делает обсуждение чего-либо после этого абсолютно бессмысленным.

Весь дизайн современного C++, снизу доверху, рассчитан на оптимизирующий компилятор. Весь. Обсуждать что там проихсходит при компиляции с -O0 (а это — умолчаение по историческим причинам) бессмысленно.

Я затупил и вместо define-а самостоятельно заменил max на if ручками (который Вы назвали другим алгоритмом).
Да, это другой алгоритм, потому что он не производит записи в некоторых случаях. За счёт проблемы алиасинга компилятор не всегда может подобное преобразование сделать.

Кажется, что это я криворукий, но пока не знаю, как это сделать нормально.
Нормально — это как? Так, чтобы оптимизатор сломался? Это можно сделать, более того, я это наблюдал лично. Когда количество элементов в одной функции (после инлайн-подстановок) превышает определённый порог (не знаю точно в чём он меряется но в том случае, когда мы упёрлись в порог на MSVC речь шла о функции в 17 тысяч строк) — то, действительно, могут быть проблемы с оптимизацией.

На clang и GCC мы такого не наблюдали — да и вряд ли вы каждый день пишите функции в 10 тысяч строк.

Надеюсь, что сейчас все по модулю странной проблемы с макросами нормально.
Что вы подразумеваете под странной проблемой с макросами?

Вы по-прежнему реализуете разные алгоритмы. Но для реализации вашего алгоритма #define не нужны — достаточно вспомогательной функции. Примерно так. Заметьте, кстати, что версия со вспомогательной функцией оказалась короче — хотя и всего на две инструкции. Версия с макросами делает так:
        mov     eax, DWORD PTR T[0+rax*4]
        mov     DWORD PTR T[0+rsi*4], eax
        mov     ecx, DWORD PTR T[0+rcx*4]
        cmp     eax, ecx
        cmovl   eax, ecx

А версия со вспомогательной функцией так:
        mov     edx, DWORD PTR T[0+rdx*4]
        cmp     DWORD PTR T[0+rcx*4], edx
        cmovge  edx, DWORD PTR T[0+rcx*4]

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

Это, на самом деле, несолько странно — это типичная ситуация, но я не думал, что она проявится на столь простой функции. Скорее я ожидал одинкового кода…

P.S. Да, версия с функцией — на шесть строк длиннее, да… но вот это ж как раз чистое олимпиадничание: экономия на строчках, на однобуквенных названиях переменных и прочем. Может быть уместно в условиях жёсткой нехватки времени, но в спокойной обстановке я бы, скорее, макросы не использовал бы.
Кажется, что все кроме первого пункта ссылается на прошлую версию моего комментария, который я судорожно пытался изменить в те 30 минут, что дает хабр. Для того, чтобы убедиться, что я не набагал с макросами я сбрасывал буфер после каждого запроса, что замедлило работу программы в 3 раза. Когда я замерял время работы я забыл поменять обратно endl на '\n' и был крайне удивлен ухудшением производительности. Отсюда:
по модулю странной проблемы с макросами
Кажется, что это я криворукий, но пока не знаю, как это сделать нормально.


Да, версия с функцией — на шесть строк длиннее

Казалось бы, наоборот, версия с функцией короче на 2 строки, потому что в ней нет define-ов.

макросы не использовал бы

Я не говорил, что макросы это хорошо или что это плохо.
Обсуждать что там проихсходит при компиляции с -O0 (а это — умолчаение по историческим причинам) бессмысленно.


Видимо, да. Спасибо.

Итого: бенчмарк сравнивал результаты без оптимизаций, которые может делать компилятор. Без них, что никогда не используется, быстрее макрос. С ними, что происходит всегда и везде, код не ухудшает свою производительность при использовании стандартных функций.

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

P. S.

в условиях жёсткой нехватки времени

Тогда используют очень небольшое кол-во макросов, в которых нельзя ошибиться, поскольку не поддерживаешь код и все макросы пишешь либо сам, либо они общепринятые в среде:
#define ff first
#define ss second
#define for(i, n) for(int i = 0; i < n; i++)

и не более 5-10 других для сокращения объема кода, чтобы можно было пафосно писать без автодополнений IDE, а код становился более читаемым для спортивных программистов.
Конкретно я пользуюсь только двумя верхними и когда нужно тем, что написан ниже:

Временами используется:
#define int long long
если внезапно оказалось, что где-то что-то переполняется, и нужно срочно это исправить.

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

Когда я ездил по олимпиадам, на C/C++ практически никто не писал. Самым часто используемым языком был Pascal/Delphi, потому что в нём были киллер-фичи: проверки на переполнение и выход за границу массива из коробки, удобая IDE. Те же, кто писал на C/C++, тратили существенно больше времени на написание и отладку кода.

А необходимости в макросах я не видел: скорость набора кода не является ограничителем.
.first и .second могут загромождать формулы и строчки.
#define int long long < — нужен не для скорости набора кода, а для того, чтобы быстро убрать переполнение по всему коду

скорость набора кода не является ограничителем

Это, конечно, правда.

на C/C++ практически никто не писал
Сейчас зависит от уровня соревнований и сложности конкретной задачи. На соревнованиях высокого уровня это обычно основной язык. А так, python, Pascal/Delphi тоже используются.
Ну это уже очень далеко от темы. Давайте скажем так: я видел много олимпиадников, кто-то использовал #define ff first, кто-то нет, но ни один из них ни разу не пробовал ничего подобного к нам в Git в программу на миллионы строк что-то подобное заливать.
Не применяли или не видели? Не все их используют, естественно. Но они частенько встречаются в коде и это является нормальным, поэтому я сказал, что они стандартные.

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

Вот посылки с последнего раунда на CF:
#define int long long
пример 1
пример 2
пример 3
пример 4
пример 5

Я верю, что #define-ы на pair<int, int> на бывают разные. По моему субъективному опыту ff и ss самые популярные, но как оказалось после просмотра посылок последнего раунда, это правда только для локального сообщества, в котором я нахожусь.
посылка, но это из московской сборной
пример 2
пример 3
пример 4

#define for(i, n)…
здесь For, а не for
здесь тоже For, а не for
здесь он вообще как FOR
Кажется, что этот макрос чаще пишется с большой буквы, что является неожиданностью для меня.

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

Что такое «спортивное программирование»? Чем там меряются? У кого exe-шник компактнее?

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