Comments 77
EDIT: вот очень годные примеры использования.
К сожалению, X-macro не всегда можно адекватно заменить на шаблонный код. Классическое применение — функция-маппер вариантов перечисления в строку и обратно. А ещё перечисление может "внутри" мапиться на что-нибудь нетривиальное. Справедливости ради, это действительно один из немногих случаев, когда макросы реально полезны.
Да, а что с отладкой шаблонного кода? Мало с ними сталкивался, так что интересно, что меня там ждёт…
С т.з. отладчика лямда — это один оператор (если в дизассемблер не лезть), что очень «облегчает» их отладку.
P.S. В С++ (по крайней мере, в VS2017) не испытывал проблем отладки шаблонных классов
Кажется последняя популярная библиотека, которая реализовывала всё примерно так, как это изначально было задумано — это Qt, где вместо того, чтобы насиловать препроцессор написали moc. Но это, в некотором смысле, «последний из могикан»: Qt писалась во времена, когда подход «библиотека должна быть совместима с моей любимой IDE, а иначе я её использовать не буду» ещё не стал доминирующим.
В современном же мире люди будут скорее насиловать мозги и PVS-Studio тонной макросов, чем подключат к проекту ещё один препроцессор (что, кстати, иронично, так как, в отличие от Turbo Pascal и Turbo C, современные IDE это позволяют сделать).
Я нашёл для себя неплохое правило. Все макросы именовать начиная с $. Частью идентификатора он быть не может, зато препроцессоры "большой тройки" воспринимают его как нормальный допустимый символ. Заодно убирает проблему конфликтов имён и для аргументов макросов.
Все макросы именовать начиная с $. Частью идентификатора он быть не можетЭто кто вас так жестоко обманул? Идём по ссылке — а потом в магазин за губозакатывательной машинкой.
зато препроцессоры «большой тройки» воспринимают его как нормальный допустимый символ.Не «зато». Стандарт действительно оставляет это на усмотрение разработчиков компилятора, но фишка тут вот в чём: если компилятор таки не считает доллар валидным символом, то и в препроцессоре он запрещён тоже. А если считает — так он, конечно, разрешён везде. Чтобы программы с VAX'ов, где он часто разработчиками на C использовался для имитации
namespace
ов было удобнее переносить. Так что применяем машинку ещё раз.P.S. В MSVC в полном соответствии с документацией (скроллить до фразы The dollar sign $ is a valid identifier character in Visual C++) доллар разрешён всегда, в clang'е и gcc — да, это опция, которую можно включать и выключать, но, опять-таки, везде.
А где же полноценная проверка Миднайт коммандера?
На сколько помню Apple отказалась от GCC из-за смены лицензии на GPLv3 после версии 4.2.1 а не из-за макросов в коде
Если известно, что входным значением всегда является константа, то можно добавить constexpr
и быть уверенным, что все вычисления произойдут на этапе компиляции.
К сожалению быть уверенным можно только если описать функцию как consteval
(C++20). Если у вас более старый диалект языка, то нужно результат работы constexpr
-функции засунуть в constexpr
-переменную — только тогда можно быть в чём-то уверенным.Если их вызвать в функции, которая вызывается в цикле, а компилятор решит ее заинлайнить…
Ну вы поняли, что будет :)
Вообще, alloca — изощренный способ стать инвалидом.
Там обычно много boilerplate-кода вида:
switch (state)
{
case 0: // начальное состояние
// много буков
state = __LINE__; return true; case __LINE__: // такое повторяется при каждой смене состояния или в контрольной точке
// много буков
default:
return false;
}
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
. При использовании «охранительного» класса будет точно также создаваться временный объект и передаваться ссылка на него, а если попытаться взять адрес — то вас об этом вежливо известят.Можно защититься через enum:
enum A { MyConstExprValue = 5} ;
auto x = &(A::MyConstExprValue); // нельзя
auto x = std::addressof(A::MyConstExprValue); // и так нельзя
И тут на помощь приходит std::addressof :DMЧто значит «приходит на помощь»? Основной принцип в C++, описанный ещё в изместной книжке — это то, что все защиты в C++ работают против случайного непреднамеренного доступа, но не против кражи или взлома.
Вы часто вообще используете
std::addressof
? Достаточно часто для того, чтобы могли случайно создать проблему (которую вы, кстати, до сих пор отказываетесь описывать)? Покажите — я хочу это видеть!Заметьте, что ваша программа прекрасно компилируется, если заменить в ней 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
отличаться — а иначе какой смысл? Зачем в языке ещё одна сущность, которая полностью дублирует другую?На 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.»
constexpr
-переменной — это ошибка компиляции. А то, что у такой переменной можно взять адрес — так это преимущество, не недостаток. Особенно в C++17, где constexpr
-переменная — не static
, а inline
…#define TRUE FALSE
#define PI 3.14268265359
Важно, что макросы провоцируют ошибки.
Эээ, нет. Обе приведенные в пример ошибки провоцирует название макроса как одной из стандартных функций. Если макрос будет называться is_space_or_tab
, то никому и в голову не придет ожидать от него поведения функции isspace
.
Второе: усложняется чтение кода
Ну, тут как посмотреть. Или 20 строк, в которых 2-3 строки меняются, повторенных 40 раз, или 40 строк макровызова с четко видимыми аргументами. Макрос в этом случае еще и от опечаток страхует.
Получается, что макросы нужно использовать с повышенной аккуратностью или вообще не использовать.
C++ вообще нужно использовать с повышенной аккуратностью :)
Если макрос будет называться/blockquote>
Я один использую редакторы отделяющие разной подсветкой функции и макросы?
В общем случае редактора маловато будет, IDE нужна, которая поймет, что в данном месте isspace
— это функция из стандартной библиотеки, в месте 2 — функция из библиотеки в соседнем файле, а в месте 3 — макрос, определенный в дебрях системных библиотек, если стоит дефайн X и не стоит дефайн Y. Причем все 3 случая могут быть в одном файле
is_space_or_tab(readCharFromFile());
Это было только возражение к тому посылу, что макросы плохи, потому что могут быть приняты за стандартную функцию. А если бы isspace
была бы функцией, это что, как-то кардинально бы поменяло ситуацию? Точно также писавший смотрел бы справку по стандартной библиотеке, а вызывался бы собственный костыль.
Кроме того, некоторые вещи без макросов просто не реализовать — например, превращение токена в строку. Никакой шаблон или constexpr
вам это не сделает. Особенно, если его нужно использовать и как строку, и как идентификатор. А очень часто если противник макросов — то не признает их ни в каком виде. При этом в описанной ситуации очевидно отказ от макроса делает только хуже.
А если бы isspace была бы функцией, это что, как-то кардинально бы поменяло ситуацию?
А разве в этом случае код бы собрался без проблем?
А очень часто если противник макросов — то не признает их ни в каком виде.А как такие умники код тестируют? GTest им не годится, значит… что? И как оно выглядит?
Мне кажется что разумное отношение — это некий «коэффициент толерантности»: поскольку макросы более опасны и их использование усложняет отладку, то их следует избегать… кроме тех случаев, когда они позволяют писать существенно меньше кода.
Что именно «существенно» — вопрос обсуждаемый, но если у меня в проекте человек, который готов вместо макроса на пять строк породить тысячу строк безумного кода на шаблонах… я, пожалуй, с ним работать не смогу.
Это тоже только «отмазка».
Это не правда. В спортивном программировании замена max и min на макросы позволяют немного ускорить код, превращая T/L в OK, если в задаче это критичные операции.
std::max
на макросы? Там, я извиняюсь, другой алгоритм, а не замена std::max
на макросы.Если же взять этот код, заменить
std::max
на стандартный макрос, то… та-да… скомпилированный код у двух вариантов будет идентичен до последнего бита!Ни и откуда у идентичного кода ускорение на 8%? Может вы обладаете сильным чувством веры и умеете ускорять своей верой бинарники на 8% — но мне это, увы, не удаётся.
P.S. А вот вопрос на тему «а почему и как отказ от использования
std::max
(или макроса MAX
) может ускорить ваш код» — это действительно хороший вопрос для собеседования. Но к «вере в макросы» он отношения не имеет.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. Да, версия с функцией — на шесть строк длиннее, да… но вот это ж как раз чистое олимпиадничание: экономия на строчках, на однобуквенных названиях переменных и прочем. Может быть уместно в условиях жёсткой нехватки времени, но в спокойной обстановке я бы, скорее, макросы не использовал бы.
по модулю странной проблемы с макросами
Кажется, что это я криворукий, но пока не знаю, как это сделать нормально.
Да, версия с функцией — на шесть строк длиннее
Казалось бы, наоборот, версия с функцией короче на 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
если внезапно оказалось, что где-то что-то переполняется, и нужно срочно это исправить.
Вот сколько я ни ездил по олимпиадам, ни разу ваших "общеизвестных" макросов не применял.
А необходимости в макросах я не видел: скорость набора кода не является ограничителем.
#define int long long < — нужен не для скорости набора кода, а для того, чтобы быстро убрать переполнение по всему коду
скорость набора кода не является ограничителем
Это, конечно, правда.
на C/C++ практически никто не писалСейчас зависит от уровня соревнований и сложности конкретной задачи. На соревнованиях высокого уровня это обычно основной язык. А так, python, Pascal/Delphi тоже используются.
На 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
Кажется, что этот макрос чаще пишется с большой буквы, что является неожиданностью для меня.
Возможно, что их начали использовать активно не в то время, когда Вы были на олимпиадах.
Макросы — это плохо. Что ещё плохо? Читайте подборку 60 антипаттернов для С++ программиста!
Вред макросов для C++ кода