Многострадальный C++ и системы сборки -13


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

Грехи синтаксиса C++

Точка с запятой;
Вот зачем в Си до сих пор пишут точку с запятой? Существует ли случай, в котором отсутствие ; могло бы вызвать критическую ошибку всей программы? Нет, это придаток доставшийся программистам с древних времён и исбавление от него не вызвало бы никаких проблем - для совместимости старые программы бы просто учитывали точку с запятой, а новые бы понимали что отсутствие символа можно проигноровать. Почему после неймспейсов точка с запятой не нужна, а после структур и классов нужна? Это как-то влияет на выполнение программы? Не влияет ни на что. От фигурных скобочек я тоже был бы рад избавиться, но это уже слишком питон получается да и обратную совместимость было бы сложнее поддерживать чем если бы просто убрали ;

Длинная запись reinterpret_cast<type>(val) и подобных кастов
В старой сишке обходились записью (type)val для преобразования типов, но в плюсах добавили новые касты с защитой от неправильного использования, что хорошо, но почему их запись такая многосимвольная? Можно было бы просто сделать scast, dcast и rcast. Да, сокращения слов до трёх букв считается плохим тоном, но касты в коде встречаются часто и их многократное использование в одной строке очень сильно удлинняет код (у автора нет два широких экрана, ему больновато читать длинный код).

Отсутствие генерации имён
Препроцессор всё время хотят выпилить из C++, но до сих пор не добавили аналога для ## и #, которым можно было бы создавать имена функций из параметров, а так же переводить их в строки и наоборот. Шаблоны тоже могут сделать вам функцию, но выглядеть это будет не красиво. Макросами же можно сделать более компактный код, ну и одновременно более запутанный. К примеру посмотрите сгенерированные макросом функции пиксель-блендинга в ffmpeg там важно вставить функцию смешивания пикселя, на шаблонах бы вам пришлось писать лямбды, которые наверное бы воспринимались как std::function и медленно бы вызывались.

Inline не то, чем кажется
Смысл в инлайне был во встраивании кода функции за место её вызова. Это лишь рекомендация неучитываемая компилятором при релизе и очень даже учитываемая при дебаге, но в C++ inline используют не по прямому назначению. Создатели header-only кода использую это слово для того, чтобы компилятор не ругался на повторное объявление функции. Приписка инлайна ко всему лишь увеличивает код, но это не значит что автор кода хотел чтобы его функции встраивались, он лишь использует инлайн как трюк. Вообще, в одном китайском эмуляторе сеги я видел примерно это:
constexpr static inline auto func(args) -> int noexcept const
Я чуть чуть преувеличил конечно, но будьте уверены, в этом стиле выполнены все функции в исходниках этого эмулятора. Понятно же что делает каждое из этих ключевых слов и зачем так было написано, но складывается ощущение, что синтаксис C++ вынуждает программиста делать всё что угодно, только не писать саму программу.

Register
В С++ уже давно убрали это ключевое слово объясняя это как раз тем, что оно часто игнорируется. Кому это мешало? Я например использовал register для индексов в циклах при дебаге. Вас же не интересует как быстро работает сам цикл, вы же хотите минимизировать его скорость работы и замерять скорость того, что в этом цикле.

Реализация компиляторов

Полной реализации того, что понапридумывали в стандартах, мы увидим разве что через 100 лет.

Рандом

Видели мем с картинки? А В MINGW ЭТО НЕ МЕМ!. Реально, до версии 10 std::random_device мог возвращать вам ноль, а мог и вообще не скомпилироваться. Тот кто это придумал видимо был гением, это ведь ещё в C++11 было добавлено, а исправлено только в 2018-м.

std::execution::parallel_policy
С C++17 доступен std::execution::parallel_policy и другие политики выполнения для стандартных функций STL - вы може включать распараллеливания, можете даже УСКОРИТЬ std::sort. В чём проблема? В том что в Mingw вам понадобится отдельно докачать Parallel STL иначе не заработает.

В одном компиляторе есть то, чего нету в другом
Код написанный для разных компиляторов впринципе может быть несовместим, приходится писать разные макросы для обхода проблем. Так же бывает что у одного компилятора есть то, чего нету в стандартной библиотеке, например hash_map. Где-то что-то даже может не собраться, потому что софтина использует какой-то древний вызов сишной функции с какого-то "другого" компилятора.

Медленная сборка

Header-only библиотеки
Использование библиотек написаных внутри одного .h файла считается модерн стилем. Можно вообще написать всю программу на одних хедерах и у вас будет только main.cpp для компиляции. Удобно, никаких лишних повторений кода для описания интерфейсов, можно прописать только один файл для компиляции, но вот время компиляции такой программы может затянуться секунд на 30, что при частой пересборке может оказаться болезненно, а пересобирать код придётся часто, ведь каждый раз компилироваться будет ВСЁ ЗАНОВО. В модульной архитектуре .h/.cpp такой проблемы нет, как и нет проблемы с перекрёстными ссылками. Большинство систем сборки умеют следить за изменениями в коде модулей и собирать только эти изменённые модули не пересобирая всю программу. Многопоточность в этом случае поможет собрать модули быстрее, но в конце всё упрётся в линкер, он быстрее собирать не станет, можно разве что собрать код с -O0. Ситуацию так же спасает сохранение кеша компиляции, но при header-only подходе это не поможет. Даже библиотека stb_image имеет макрос для включения кода с реализацией, чтобы вы могли отдельно заинклудить эту либу в каком-нибудь .cpp файле и один раз там её собрать. Да даже для iostream придумали iosfwd. Ситуацию должны исправить C++20 модули, но в них тоже есть проблема, о которой ниже.

C++20 Modules
Давно бы пора добавить импорты и экспорты модулей в плюсы, учитывая что такие вещи были уже ассемблере для микроконтроллеров PIC16. Уже сейчас вы можете писать модульный код нового поколения, но это только на бумаге. На практике вам нужно собрать код со специальными флагами разными для GCC и Clang, + для них ещё и по умолчанию заданы разные расширения файлов типа .cxx. Можно конечно указать и своё расширение, но почему это не сделали автоматически? GCC и Mingw-w64 уже умеют в модули, но с 11-й версии. Для Clang'а же нужно ещё и прописать карту модулей в отдельном файле, чего мне бы делать не хотелось. Сейчас же вы можете довольствоваться ошибками компиляции и неполным переносом стандартной библиотеки, хотя обладатели MSVC должны уже иметь std.core и подобные вещи для импорта. Кстати, модули нужно собирать в первую очередь, а только потом мейник, к сожалению система сборки Scons такого не может, но может CMake.

Кодировка Windows

В волшебном мире линукса вам int main(argc, argv) может принять аргументы в UTF-8 и даже не поперхнуться, а вот на винде вам понадобится использовать системнозависимый функционал или wmain, которого нету в GCC и Clang (дефайн UNICODE не прокатит и сборка с ресурсом в виде .xml кода не сработает на Win7). Бывает и что в папке с русскими символами возникают проблемы с путями в самой программа, особенно в Windows XP.

Системы сборки

CMake
Рано или поздно вы можете задолбаться собирать ваш большой проект из скриптов на баше или батнике на винде. IDE сами всё разруливают, но бывает что хочется более кроссплатформенный вариант. Чтож, у нас есть CMake, к сожалению его код на вид ужасен, а в билдах сборки опенсорс проектов сам чёрт ногу сломит. Только в CMake у вас есть 101 способ сделать одно и то же действие функциями из разных версий.

SCons
Предоставляет возможность весело и просто писать питоновские скрипты для сборки C++ прог. Умеет в кеш компиляции и разбиение скриптов на модули. К сожалению не понимает C++20 модули и не предоставляет информации о текущей архитектуре, хотя где-то там внутри знает на чём он запускается. Вам самим нужно прописывать кроссплатформенное поведение.

END

Большинство проблем натянуты и мучают лично меня одного, просто захотел выссказаться. Пишите чё думаете и что вам не нравится в C++. Интересно посмотреть реакцию на инакомыслие, более 44 коментов всяко лучше чем 0 как на моей прошлой статье.




Комментарии (111):

  1. ShadowTheAge
    /#23997083 / +7

    Существует ли случай, в котором отсутствие; могло бы вызвать критическую ошибку всей программы?

    Да.


    auto p = a; -b;
    auto p = a -b;


    auto p = a; (b=5);
    auto p = a(b=5);


    Я не сишник, уверен сишники сейчас еще десяток примеров приведут.

    • dmitrmax
      /#23997357 / +4

      Сейчас автор скажет, что разделять надо переводами строк и тогда всё будет ОК =)

      • AtariSMN82
        /#23998675 / +2

        Не, автор скажет мол незачем в конце строки ставить. Для разделения a, b или a; b было бы норм

        • 4eyes
          /#23998825 / +1

          while ('\0' != (*dst++ = *src++))
              /* just copying */;

          • netch80
            /#23998985

            Ну для пустого оператора вполне можно было бы nop завести :)

      • adeshere
        /#23998681 / +2

        Я тут всего лишь "крокодил мимо", но вот у нас в фортране операторы можно разделять точкой с запятой, но при этом ставить ";" в конце каждой строки совершенно не обязательно:

        select case (int_Year)
          case(:1917);  	name=get_emperor(int_Year); 	exit main_cycle
          case(1918:1991);  return 1914
        end select

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

        Кстати, строка "return 1914"

        в фортране означает совсем не то, что некоторые могли подумать, а так называемый "альтернативный возврат". А именно, управление будет передано не на следующий после call оператор, а на 1914-ю по счету метку в списке фактических параметров процедуры. Как сказал один классик, "эта штука сильнее, чем GOTO!" (c). И - да, такой оператора call (с соответствующим списком меток) без переноса на следующую строку не напишешь ;-)

        P.S. Даже интересно, почему при такой любви к лаконичности в Си-семействе еще никто не попытался "залюбить" символ ";" в конце строчки ;-)

        • ShadowTheAge
          /#23999109

          А если хочется наоборот — одну команду на несколько строк?


          По моему все-таки в с++ и прочих джавах сделано лучше. Когда непечатаемые символы вроде переноса строки или табуляции (питон) влияют на логику программы с этим становится опаснее работать в обычных текстовых редакторах.

          • dmitrysvd
            /#23999479 / +1

            Табуляцию не принято использовать в питоне, только пробелы. С ними не возникает проблем из-за разной длины табуляции.

          • adeshere
            /#24002891

            А если хочется наоборот — одну команду на несколько строк?

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

            надо написать явно:
            Res = EXP (Y) + &        ! The initial statement line
                  EXP(-Y)            ! A continuation line
                  
            MyText  = "This is &   
                  &a text"

            Кстати, я сам не знал, что у нас можно разбивать литерал на несколько строк. Всегда писал что-то наподобие вот такого:

            MyMessage  = "Это первая половина сообщения "// &
            "а это вторая"

            Может, это вопрос привычки, но в таком формате мне легче читать и с пробелами как-то проще...

            Понятно, что у фортрана в силу древности происхождения набор символов изначально весьма ограничен. Например, логическое И пишется, как Condition1.AND.Condition2, а побитовое - это вообще функция (пишется, как IAND(Int1,Int2); впрочем, если она нужна часто, можно перегрузить оператор). Поэтому найти свободный символ для знака продолжения строки, когда такая идея возникла, было не сложно ;-).

            Но даже если в языке все символы уже заняты, кто мешает использовать для знака продолжения оператора двойной символ? Ведь даже в очень-очень просторном литературном фортране строки с продолжением встречаются крайне редко. А в языках, стремящихся к лаконичности, наверное, еще реже? Поэтому чисто теоретически (глядя со стороны) возникает впечатление, что изредка написать дополнительный символ продолжения оператора было бы быстрее и проще, чем ставить точку с запятой в конце каждой строки. Но тем не менее это не принято и такой вариант даже не обсуждается. Если это не секретная информация, было бы интересно узнать - почему?

      • Bobovor
        /#23999301 / +1

        И прав будет. Всрато писать - ловить всратые ошибки. Play stupid games, win stupid prises.

        Написать повсратее это вообще спецолимпиада с/с++. Хотя там можно писатт очень читаемо с нулевой ценой.

        Ps сам не против точки и скобочек, с ними как то более явно что ли...

    • qw1
      /#23998771 / +3

      Существует ли случай, в котором отсутствие; могло бы вызвать критическую ошибку всей программы?
      Классический пример из K&R
      void strcpy(char* dst, const char* src)
      {
              while (*dst++ = *src++);
              *dst++ = 0;
      }

      • AtariSMN82
        /#23999075 / -2

        Похоже что пока в C++ будет возможность писать такой код, он всегда будет останется с точкой с запятой

      • 0x4eadac43
        /#24004043

        Если пофантазировать

        while (*dst++ = *src++)
          continue
        *dst++ = 0

        • qw1
          /#24004513

          Но автор хочет не так, он хочет

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

          • 0x4eadac43
            /#24007099

            Так, а вот сейчас я понял, что ничего не понимаю в том, что хотел автор :-) Надо было избавиться в синтаксисе от точки с запятой, или же, надо ее оставить, но так, чтобы ее не было (и как это)?

            • qw1
              /#24007825

              Он считает (считал), что если точку с запятой сделать опциональной, то ничего не сломается, т.к. всегда можно понять из контекста, лишняя она или не лишняя. Мой пример показывает случай, когда поведение меняется, и компилятор автоматом не сможет вычислить, пропущена точка с запятой умышленно, или это «новый синтаксис».

    • StepanStulov
      /#23999211

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

  2. Tzimie
    /#23997103 / +1

    но вот время компиляции такой программы может затянуться секунд на 30

    Эх, в 87 году компиляция программы в 1200 строк на Паскале занимала 30 минут

    • AtariSMN82
      /#23998691

      Игры кое-какой фирмы на движке Alien Engine с доработками как раз компилились у меня 30 мин на компе с Core 2 Quad, но это хотя бы один раз надо так потерпеть при полной пересборке. В офисе той фирмы даже поставили сервер компиляции

  3. Izaron
    /#23997125 / +2

     Вообще, в одном китайском эмуляторе сеги я видел примерно это:
    constexpr static inline auto func(args) -> int noexcept const

    Не читайте до обеда советских газет исходники китайских эмуляторов.

    [dcl.consexpr] - constexpr-метод неявно является inline.

    static inline бессмысленный и работает как просто static. И сам static не нужен, когда есть constexpr. (Если этот метод глобальный)

    Про "второе значение" слова inline, про встраивание кода - примерно с 2013 года Clang перестал ставить таким методам флаг inlinehint в своем промежуточном представлении, и сейчас это значение утратилось. У других компиляторов скорее всего так же.

    • NTDLL
      /#23998177

       Вообще, в одном китайском эмуляторе сеги я видел примерно это:

      constexpr static inline auto func(args) -> int noexcept const

      Какой смысл в модификаторах static inline?

      Если подразумевается, что функция вычисляется на стадии компиляции и в сегменте кода (или ещё где либо) её просто нет.

      • Videoman
        /#23998549 / +1

        Судя по тому, что у функции модификатор const это мембер класса и смысл в static как раз может быть, а вот constexpr и так автоматом делает такую функцию inline. Т.е. как раз inline тут лишний.

    • AtariSMN82
      /#23998701

      да, constexpr static inline сами себя аннигилируют, но там это видимо сделано на всякий случай

  4. Ritan
    /#23997145 / +12

    А В MINGW ЭТО НЕ МЕМ!.

    В том что в Mingw вам понадобится отдельно докачать Parallel STL

    GCC и Mingw-w64 уже умеют в модули, но с 11-й версии

    Где здесь проблема C++?

    Бывает и что в папке с русскими символами возникают проблемы с путями в самой программа, особенно в Windows XP.

    А MS-DOS 6.22 не поддерживает имена файлов длиннее, чем 8.3. Нам как, уже начинать считать проблемы OS, вышедшей всего-то за 7 лет до Windows XP, как критичные?

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

    В нормальном коде касты встречаются достаточно редко

    вам пришлось писать лямбды, которые наверное бы воспринимались как std::function и медленно бы вызывались.

    Это "наверное", говорит очень многое

    • AtariSMN82
      /#23999023 / -5

      Где здесь проблема C++?

      Проблема компилятора

      Нам как, уже начинать считать проблемы OS, вышедшей всего-то за 7 лет до Windows XP, как критичные?

      На Доске может никто и не сидит но вот ноуты с икспишкой попадаются часто

  5. amarao
    /#23997231 / +2

    Не знаю как в C, а в Rust - ; - это очень важная часть синтаксиса, она делает возвращаемый тип unit (()).

    Т.е. fn foo() -> i32 {4} и fn foo() -> i32 {4;} - это две большие разницы, второе даже не скомпилируется. Благодаря тому, что ; разделяет выражения, можно писать блок ({}) в любом месте.

    Например, так:
    let x = {println("hello here"); 4};

  6. MarazmDed
    /#23997269 / +20

    Можно было бы просто сделать scast, dcast и rcast

    Офигенно длинные выражения для кастов - не ошибка и не случайность. Это сделано ПРЕДНАМЕРЕННО. Чтобы вызывать чувство дискомфорта. Каст - исключительная конструкция, которой в норме не должно быть. Если увас много кастов - что-то в коде у вас не то. Говнокодом попахивает.

    а после структур и классов нужна?

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

    Лично меня ни скобки, ни точки с запятой не напрягают.

    • dmitrmax
      /#23997447 / +3

      Странно, что автор не пожаловался на необязательность выравнивания кода, если вы понимаете, о чем я =)

    • kamilkamilkamil
      /#23999003 / -2

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

    • AtariSMN82
      /#23999047 / -1

      Каст - исключительная конструкция

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

      • rat1
        /#23999107 / +6

        Если вам приходится делаеть динамик каст, то вы априори что-то не то надизайнили) Тем более могут быть определенные проблемы с RTTI, в зависимости от компилятора.

      • allcreater
        /#24002003 / +1

        Да, в IO функциях кастовать не воспрещается, и временами вовсе необходимо: передать массив как массив байт, а так же сделать memcpy(не reinterpret_cast!) при обратном преобразовании массива байт в объекты.

        C++20 для обоих случаев даже специальные библиотечные функции получил: std::as_bytes, std::bit_cast.

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

      • MarazmDed
        /#24005639 / +1

        Ну а если надо кастовать данные в байты при работе с файлами

        Это и есть та самая редкая исключительная ситуация.

        или сделать динамик каст с базового класса, разве это плохо?

        Это - очень плохо. Нарушение буквы L в принципах SOLID. Базовый класс не должен знать ничего про потомков. Как раз здесь и должна сыграть свою роль ужасность конструкции каста.

  7. bfDeveloper
    /#23997377 / +13

    Весь раздел про синтаксис - демонстрация его полного незнания и непонимания. Хоть бы почитали, откуда взялась эта точка с запятой, особенно после классов.

    В некоторых других моментах есть зерно истины, но накидали в одну кучу так, что пользы - ноль. Даже крик души полезно структурировать.

    • AtariSMN82
      /#23999091 / -1

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

  8. dmitrmax
    /#23997433 / +2

    В волшебном мире линукса вам int main(argc, argv) может принять аргументы в UTF-8 и даже не поперхнуться, а вот на винде вам понадобится использовать системнозависимый функционал или wmain, которого нету в GCC и Clang

    Участвовал в проекте, где по неосторожности ради разработчиков, пишуших под виндой на старте проекта было принято решение использовать wide character'ы для мультиплатформенности и соответственно std::wstring. В итоге:
    1) Теперь всё это дерьмо в Linux (а это таргет платформа) конвертируется из utf-8 в UCS-4, что сжирает порой много времени на работу iconv
    2) В винде всё равно используется кастрированный юникод на два байта шириной.
    3) Везде приходится писать L"".

    И так далее. Иногда это вылезает в самых неожиданных местах.

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

    Если вы пишите юзерский софт - закройте эти проблемы библиотекой типа Qt. Если пишете серверный/системный софт - просто забейте на винду.

    • Videoman
      /#23997649

      … на старте проекта было принято решение использовать wide character'ы для мультиплатформенности и соответственно std::wstring. В итоге:
      А какая связь между UNICODE конфигурацией WinAPI и выбранным вами std::wstring. Что мешало выбрать std::string или std::u8string и использовать UTF-8?

      • dmitrmax
        /#23997773

         Что мешало выбрать .... std::u8string ....?

        Как минимум 2010 год.

        Что мешало выбрать std::string .... и использовать UTF-8?

        И много ли полезного можно сделать с UTF-8 под Windows? Напечатать по-русски в консоль? Вывести SetWindowText'ом? Даже в блокноте не посмотреть, если там есть non-ASCII. Я уже не говорю о том, что данные, которые приходилось обрабатывать, надо было конвертировать из архаичных кодировок сначала, наверное, в Windows Unicode, а потом в UTF-8 переконверчивать? Ну и к тому же, это потянуло бы какие-то внешние библиотеки, так как готовых средств по работе с UTF-8 на тот момент в Windows я не помню. Как сейчас не знаю.

        • Videoman
          /#23997885 / +1

          У меня тоже весь код кроссплатформенный. Использую именно такой подход с 2006 года. Подход следующий, что касается С++: строки std::string в кодировке UTF-8. В платформозависимых местах под Windows происходит конвертация в UTF-16 и обратно в UFT-8, если надо. Всё, никаких проблем за всё это время нет. Никаких этих ваших уродских L"". Никаких проблем в Linux. Чем вам такой подход не нравится?
          И если серьёзно подходить, то никакая это не проблема С++. Это проблема неудачно выбранного технического решения. Максимум проблема API на разных OS.

          • dmitrmax
            /#23998183 / +3

            Вообще-то я этот тред и начал для того, чтобы стало понятным, что это не проблема C++.

        • mayorovp
          /#24000343

          Блокнот давно уже поддерживает UTF-8.


          В "десятке" это делается вот так:


          Скриншот

          В более старый версиях некоторых пунктов не было, но поддержка UTF-8 точно была.

    • AtariSMN82
      /#23999099 / -1

      Лучше тогда сразу за место wstring брать u8string и u8"Строки"

  9. technic93
    /#23997511 / +3

    Подучите сначала современный С++, а потом критикуйте.

    • Koval97
      /#23999443 / +1

      Полностью согласен. Как говорится, сказал как отрезал, ни добавить, ни убавить.

  10. Videoman
    /#23997539 / +4

    Почему после неймспейсов точка с запятой не нужна, а после структур и классов нужна?
    Потому-что возможна декларация одновременно с объявлением.
    Можно было бы просто сделать scast, dcast и rcast
    Автору стоить почитать предметно почему специально так было сделано и выбраны длинные имена.
    constexpr static inline auto func(args) -> int noexcept const
    constexpr static int func(args) const noexcept;
    А что здесь лишнего по мнению автора?
    Так же бывает что у одного компилятора есть то, чего нету в стандартной библиотеки, например std::hash_map
    Что за контейнер такой ??? Автор, вы точно пишете на С++
    винде вам понадобится использовать системно-зависимый функционал или wmain
    Причем тут С++? В windows никто вас не засталяет, пожалуйста, объявляете main как int main(int argc, char* argv[]), но тогда и аргументы и параметры соизвольте передавать в UTF-8.
    Большинство проблем натянуты и мучают лично меня одного, просто захотел высказаться.
    Прям так же и хочется ответить. Полное ощущение что статья стоит из потока мыслей, с надерганными откуда-то случайными примерами, без малейшей попытки разобраться что и как и без понимания какие реальные проблемы есть в С++.

    • 0xd34df00d
      /#23998759 / +5

      А что здесь лишнего по мнению автора?

      Про автора не знаю, но зачем нужен constexpr, я не понимаю до сих пор. Он не входит в тип функции, это по факту всего лишь хинт компилятору на «эту функцию можно попробовать вычислить в константном контексте». Почему все функции не могли бы быть implicitly constexpr, непонятно.


      А, ну кроме возможности добавить ещё одно UB, конечно, потому что constexpr-функция, не являющаяся constexpr ни для одного набора параметров, делает программу ill-formed/NDR. Но надёжно воспользоваться этим тоже нельзя, потому что


      constexpr bool shitfunc(Nat a, Nat b, Nat c, Nat k)
      {
        k += 3;
        if (pow(a, k) + pow(b, k) == pow(c, k))
          ​return true;
        throw 42;
      }

      потребует от компилятора доказать теорему Ферма.


      Зачем это UB добавлено — тоже непонятно. Ну, кроме того, что в Комитете сидят либо напрочь оторванные от практики люди, либо психопаты, ненавидящие программистов. Учитывая, что некоторые из сидящих таки пишут код в рабочее время, это скорее второй вариант.

      • Videoman
        /#24000837 / +2

        Почему все функции не могли бы быть implicitly constexpr, непонятно.
        … в Комитете сидят либо напрочь оторванные от практики люди, либо психопаты, ненавидящие программистов.
        Как не парадоксально, но может оказаться совсем наоборот — эту фичу пилили какие-нибудь практики, которые решали свою, сугубо локально задачу, которые были так этим заняты, что у них не было уже сил поднять голову и осмотреться по сторонам. Об этом косвенно свидетельствует наличие проблемы на которую я наткнулся в своём пет-проекте. Вот как можно было в стандарте зафиксировать, что constexpr метод может отработать во время компиляции, а может и не отработать, но при этом не подумать, что бывает что реализации существенно отличаются и не дать инструментов которые дали бы возможность определить в каком режиме сейчас находится код ?! И только в С++20 вдруг очнулись и добавили std::is_constant_evaluated().

        • 0xd34df00d
          /#24002547 / +2

          Вот как можно было в стандарте зафиксировать, что constexpr метод может отработать во время компиляции, а может и не отработать, но при этом не подумать, что бывает что реализации существенно отличаются и не дать инструментов которые дали бы возможность определить в каком режиме сейчас находится код ?! И только в С++20 вдруг очнулись и добавили std::is_constant_evaluated().

          А вот тут я тоже не соглашусь :]


          ИМХО std::is_constant_evaluated() — костыль с жутко неинтуитивным поведением. Ситуации, описанные в пропозале, который я читал года три назад, выглядят какими-то уж больно натянутыми, и мне при всей моей любви к компилтайм-упарыванию ни разу не хотелось этой функцией воспользоваться.


          Если реализации сильно отличаются, то, возможно, им просто не место в одной функции.

          • Videoman
            /#24002709 / +1

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

            Если реализации сильно отличаются, то, возможно, им просто не место в одной функции.
            Речь не идет о том что бы всё помещать в одну функцию конечно, но в любом случае есть точка принятия решения и её не реализовать без std::is_constant_evaluated().
            ИМХО std::is_constant_evaluated() — костыль с жутко неинтуитивным поведением. Ситуации, описанные в пропозале, который я читал года три назад, выглядят какими-то уж больно натянутыми, и мне при всей моей любви к компилтайм-упарыванию ни разу не хотелось этой функцией воспользоваться.
            Предположим у вас есть шаблонная функция, которая вас устраивает во всех ситуация, в том числе в constexpr режиме. Теперь, предположим, что в случае если шаблонный параметр является целым, кратен 4-м байтам и код выполняется в рантайме, вы можете сильно оптимизировать алгоритм на GPU или ещё как-нибудь. Какое ваше предложение, что делать?

            • 0xd34df00d
              /#24003127 / +2

              Речь не идет о том что бы всё помещать в одну функцию конечно, но в любом случае есть точка принятия решения и её не реализовать без std::is_constant_evaluated().

              Вы её и так знаете статически, конечно же её можно реализовать без std::is_constant_evaluated()!


              Какое ваше предложение, что делать?

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

              • Videoman
                /#24006077 / +1

                Просто написать две разные функции.
                Функции физически разные, но одна должна являться перегрузкой другой, т.к. они вызываются в одном и том же контексте, в зависимости от шаблонного параметра и режима выполнения (constexpr). Можете привести пример С++ кода который так может без std::is_constant_evaluated?

                • 0xd34df00d
                  /#24007253 / +1

                  Давайте вы приведёте более-менее законченный пример с std::is_constant_evaluated(), а я его перепишу без него?


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

                  • Videoman
                    /#24008261 / +1

                    Отлично, согласен! Заодно может быть вы поможете мне решить вполне практическую задачу. Я вам буду премного благодарен. Поехали! Есть класс: long_uint.h: line 43

                    template<typename native_t, uint_t size>
                    class long_uint_t<native_t, size>;
                    Класс реализует арифметику над длинными целыми, в том числе во время компиляции. Нам сейчас не важны все операции возьмем один оператор — меньше (<): long_uint.h: line 279
                    template<typename native_t, uint_t size>
                    constexpr bool long_uint_t<native_t, size>::operator<(const long_uint_t& that) const noexcept
                    {
                        native_t digit = digits[0];
                        bool borrow = sub(digit, that.digits[0]);
                    
                        for (uint_t n = 1; n < std::size(digits); ++n) {
                    
                            digit = digits[n];
                            borrow = subb(digit, that.digits[n], borrow);
                        }
                    
                        return borrow;
                    }
                    Реализация у него в виде шаблона и очень простая: вычитаем из первого аргумента второй и если произошел перенос в старшем бите, то первый аргумент меньше. Само вычитание c переносом реализовано через функцию subb (Subtract with borrow): long_math.h: line 437. Функция subb() полностью платформонезависима и без проблем работает в constexpr выражениях:
                    template<typename type_t, std::enable_if_t<is_unsigned_v<type_t>, int>>
                    constexpr bool subb(type_t& value1, type_t value2, bool borrow) noexcept
                    {
                        type_t tmp = value1;
                        value1 -= value2;
                        bool borrow_new = value1 > tmp;
                        tmp = value1;
                        value1 -= borrow;
                        borrow_new = borrow_new || (value1 > tmp);
                    
                        return borrow_new;
                    }
                    Дальше, на некоторых платформах, например Windows, мы можем выполнять данную операцию намного эффективнее для 32-х битных беззнаковых целых, делаем перегрузку: long_math_msvc.h: line 372
                    inline bool subb(uint32_t& value1, uint32_t value2, bool borrow) noexcept
                    {
                        return _subborrow_u32(static_cast<uint8_t>(borrow), value1, value2, &value1);
                    }

                    И вот тут у нас проблема, перегрузка как раз и является частным случаем реализации которая невозможна для функций исполняющихся на этапе компиляции. В данном случае, так как запрещено использование интринсиков совместно с constexpr функциями.
                    Как бы нам тут могла помочь std::is_constant_evaluated(): мы могли бы реализовать две разные функции subb_constexpr() и subb_runtime() и вызывать одну либо другую в зависимости от контекста:
                    template<typename type_t, std::enable_if_t<is_unsigned_v<type_t>, int>>
                    constexpr bool subb(type_t& value1, type_t value2, bool borrow) noexcept
                    {
                        return std::is_constant_evaluated() ? subb_constexpr(value1, value2, borrow) : subb_runtime(value1, value2, borrow);
                    }

                    Как это сделать без std::is_constant_evaluated() я не знаю. Какие ваши предложения?

                    • mayorovp
                      /#24008603

                      Как это делать в С++ я не знаю, но нормальным решением была бы перегрузка по признаку constexpr:


                      inline bool subb(uint32_t& value1, uint32_t value2, bool borrow) noexcept {
                          return _subborrow_u32(static_cast<uint8_t>(borrow), value1, value2, &value1);
                      }
                      
                      constexpr bool subb(uint32_t& value1, uint32_t value2, bool borrow) noexcept {
                          return subb_constexpr(value1, value2, borrow);
                      }

                      • Videoman
                        /#24008697

                        В том то и проблема, что constexpr не входит в тип функции и по нему не возможна перегрузка. Это не тоже самое что спецификация const в конце метода класса.

          • technic93
            /#24002731

            на сppreference всё логично описано.

            • 0xd34df00d
              /#24003425 / +3

              Не знаю про cppreference, знаю про то, какие эффекты это имеет.


              Давайте начнём с простой функции, которая, согласно текущему стандарту, никогда не может быть вызвана в constant evaluated context:


              template<bool>
              struct Detect;
              
              template<>
              struct Detect<true>
              {
                  using type = int;
              };
              
              void foo()
              {
                  Detect<std::is_constant_evaluated()>::type test;
              }

              Соответственно, Detector<std::is_constant_evaluated()>::type test; будет валидным выражением, когда is_constant_evaluated() возвращает true, а иначе — ошибкой компиляции. Как думаете, скомпилируется?


              Скомпилируется! Почему? Потому, что is_constant_evaluated() проверяет не то, вызывается ли содержащая её функция в константном контексте, а то, вызывается ли сама is_constant_evaluated() в константном контексте. В принципе, на этом можно и закончить, но давайте продолжим.


              bool foo()
              {
                  return std::is_constant_evaluated();
              }

              даёт нам


              _Z3foov:
                      xor     eax, eax
                      ret

              Ну, то есть, функция резко стала false. Потому, что контекст больше не константный.


              Продолжаем:


              constexpr int foo()
              {
                  return std::is_constant_evaluated();
              }
              
              int global = foo();

              смотрим дизасм:


              global:
                      .long   1                               # 0x1

              Ну, то есть, true. Ок, а так?


              constexpr int foo()
              {
                  return std::is_constant_evaluated();
              }
              
              inline int bar() { return 0; }
              int global = bar() ? foo() : foo();

              Дизасм:


              global:
                      .long   0                               # 0x0

              Теперь оно false (что мне на высказанную мной ранее модель натянуть не удалось, к слову). Что, кстати, довольно иронично: компилятор в компилтайме видит, что выражение false, но оно не является компил-тайм выражением. muh logic


              Ок, а если создать временную переменную? Это — рефакторинг, который не должен иметь никакой семантической разницы для булов, верно?


              constexpr int foo()
              {
                  const bool meh = std::is_constant_evaluated();
                  return meh;
              }
              
              inline int bar() { return 0; }
              int global = bar() ? foo() : foo();

              Ага, десять раз, теперь это true:


              global:
                      .long   1                               # 0x1

              А, чисто ради лулзов, если вот так?


              constexpr int foo()
              {
                  Detect<std::is_constant_evaluated()>::type test;
                  return std::is_constant_evaluated();
              }
              
              inline int bar() { return 0; }
              int global = bar() ? foo() : foo();

              Что будет? Единичка в global? Ошибка компиляции?


              Ни то, ни другое! Этот код скомпилируется успешно (то есть, на первой строчке функции там true), но в global будет 0 (то есть, на второй строчке false).


              Чё-т как-то ну так себе логика.

              • technic93
                /#24003551 / -1

                Вполне закономерно. Вы везде пытались поменять ответ в зависимости от const_evaluated. А он предназначен для противоположной цели. И там вполне ясно сказано что тестируется выражение содержащие is_const_evaluated() а не функция в которой содержится эта инструкция. Пример с тренарником интереснее, но про него есть в документации.

                • 0xd34df00d
                  /#24007251

                  Вы везде пытались поменять ответ в зависимости от const_evaluated.

                  Я пытаюсь посмотреть, как он работает, и построить интуицию. Интуиция не очень строится.


                  А он предназначен для противоположной цели.

                  Чтобы не менять ответ в зависимости от него? Ну тогда его просто не надо вызывать.

                  • technic93
                    /#24008243

                    Да, ответ менять не надо - надо менять реализацию. Вам хочется поднять его на уровень типов, кажется что такая возможность только добавит непонятных моментов в коде. Тогда ваш рефакторинг а ля вынесли константу в новую переменную не просто поменяет true на false, а может поменять int на float.

          • crackedmind
            /#24008067

            Чтобы исправить этот костыль в С++23 добавили if consteval { } без condition

      • technic93
        /#24001379

        Пробовать вычислить любую функцию в constexpr может быть проблематично, если ее вычисление занимает много времени, или вообще не известно завершима ли она. Что компиляторы должны делать в таком случае? И что они делают сейчас интересно.

        • Izaron
          /#24001495 / +1

          или вообще не известно завершима ли она. Что компиляторы должны делать в таком случае?

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

          • qw1
            /#24001543

            Ну отлично… Программа не скомпилировалась, потому что компилятор запускался на слабом компьютере ))

          • technic93
            /#24001721

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

        • 0xd34df00d
          /#24002563 / +1

          Ну, с оптимизациями компилятор и так всё пытается вычислить и упростить, а без оптимизаций надо вычислять только то, что используется в соответствующем контексте. Условно, видим


          constexpr auto a = foo();
          SomeType<bar()> baz;

          — вычисляем. Не видим — не вычисляем.

          • technic93
            /#24002699

            Т.е. он не делает разницы для constexpr и нет функций на этапе оптимизаций?

    • AtariSMN82
      /#23999127 / -2

      почему специально так было сделано

      Уж этого я правда не почитал

      А что здесь лишнего по мнению автора?

      Можно было бы обойтись int func(args), там во всех функциях такой стиль был

      Что за контейнер такой ???

      Ой да, в std этого нет

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

      Это так и есть

  11. tangro
    /#23998009 / +5

    Боль от С++ у всех своя, кого-то одно тревожит, кого-то другое. Однако пока будут задачи, которые ничем, кроме плюсов не решить, мы будем с этим жить. В куче мест на предложение переписать всё к чертям на шарпе или JS люди улыбаются и крутят пальцем у виска. Поэтому пишите себе точку с запятой и смиритесь с этим.

    • 0xd34df00d
      /#23998721 / +6

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

  12. Playa
    /#23998581 / +4

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

    Автору советую расслабиться и получать удовольствие.

    • AtariSMN82
      /#23999163 / -4

      Ок. Особенно радует невозбранное использование любого числа точек с;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

  13. netch80
    /#23999021 / +2

    Набор боли от C++ у автора достаточно мало коррелирует с моим — настолько, что я не понимаю, что именно он пишет и почему. У меня тут следующее:

    — Само завершение операторов (statements) знаком ';' нравится больше, чем варианты его автогенерации в конце строки, если нет причин считать, что будет продолжение (как в Go, когда последним стоит операция (operator) типа "+", или как в Python, когда явная '\' или незакрытые скобки).

    А вот когда ставится ';' после '}' — тут, да, бардак. Пояснение в соседних комментариях, которое по сути сводится к отличию

    struct moo {
      int a, b;
    };
    


    от

    struct moo {
      int a, b;
    } moo1, moo2;
    


    на самом деле это последствие концептуальной слабости, что сэкономили на выделении типа:

    struct moo {
      int a, b;
    };
    moo moo1, moo2;
    


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

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

    > От фигурных скобочек я тоже был бы рад избавиться

    Ну да, форсировать группировку отступами — «это питон» со своими недостатками, которые, IMHO, чуть перевешивают достоинства (основные проблемы для меня в матчинге границ блоков в редакторах/IDE).

    > Длинная запись reinterpret_cast(val) и подобных кастов

    Согласен.

    Но тут бо́льшая диверсия (как во всей системе templates) это использование <> как скобок. Здесь спокойно хватило бы и круглых скобок.

    > Отсутствие генерации имён

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

    > Создатели header-only кода юзают это слово для того, чтобы компилятор не ругался на повторное объявление функции.

    Не видел такого. В основном это таки по назначению — просьба встроить.

    > Я например использовал register для индексов в циклах при дебаге.

    С SSA все эти рекомендации типа register действительно теряют смысл. Возможно, и до него.

    > Код написанный для разных компиляторов впринципе может быть несовместим, приходится писать разные макросы для обхода проблем.

    Очевидно, да. Но это не совсем проблема языка.

    Из того, что существенно и что автор не назвал (интересно, почему? с чем он работает?)

    — Все undefined behavior (UdB), от переполнения знаковых и до хохм типа malloc это не создание значения<a/>.

    — Порядок «тип имя» в объявлениях/определениях и все его последствия с раскруткой объявлений навыворот.

    — Восьмеричные литералы.

    — Нет именованных конструкторов (надо костылировать).

    — Сложные принципы поиска подходящей функции, очень часто неочевидные.

    — Integer promotions (давно не имеют смысла в современном мире, с редкими исключениями).

    — Отсутствие стандартизации (хотя бы в виде рекомендаций) принципов работы с байтовыми данными (как порядок битовых полей в структурах).

    — Грабли с '=' вместо '==' (варианты типа if (take a = b) тут были бы в тему).

    Это вразброд и где-то 1/3 от всех возможных жалоб, но, IMO, ближе к реальности (повторюсь — в каком именно практическом домене работает автор? Автор, признавайся!)

    • AtariSMN82
      /#23999201

      в каком именно практическом домене работает автор? Автор, признавайся!)

      Автор взялся пилить игрушку на плюсах вот и задолбался от длинных имён.

      = и == это даа, я вот однажды подумал что std::find при отсутствии результата вернёт 0 и сделал соответствующую проверку. После того как я уже позабыл об этом, стали появляться непонятные баги с искажением данных, которые долгое время не выстреливали и повявились только при релизной сборке

      • 0xd34df00d
        /#23999271 / +4

        std::find не может вернуть 0, потому что он в общем случае возвращает итератор, а итератор далеко не всегда выглядит как указатель.

      • technic93
        /#23999337 / +2

        Как это вообще скомпилировалось?

    • Kelbon
      /#23999687 / +1

      Не видел такого. В основном это таки по назначению — просьба встроить.

      Это не назначение inline, давным давно inline ТОЛЬКО про линковку

      Но тут бо́льшая диверсия (как во всей системе templates) это использование <> как скобок. Здесь спокойно хватило бы и круглых скобок.

      Вообще то в языке С++ компайл тайм вещи передаются в < >, а рантайм вещи передаются в ( ), про sizeof не нужно заикаться, т.к. это C

      темплеты C++ сильны, но крайне неудобны

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

      Все undefined behavior (UdB), от переполнения знаковых 

      Нет никакого другого способа разрешить компилятору хоть что то оптимизировать. Нет более хорошего пути чем введение УБ
      Кому нахрен нужны именованные конструкторы? Функций не хватает?
      Очень простые принципы нахождения подходящей функции - наиболее подходящая. Сложности возникают только когда вы наворотили нечто очень странное в коде. Проблема тогда не в принципе, а в коде.

      • netch80
        /#23999847 / +2

        > Это не назначение inline, давным давно inline ТОЛЬКО про линковку

        Попробуйте почитать стандарт, там интересное:

        10.1.6(2). A function declaration (11.3.5, 12.2.1, 14.3) with an inline specifier declares an inline function. The inline specifier indicates to the implementation that inline substitution of the function body at the point of call is to be preferred to the usual function call mechanism. An implementation is not required to perform this inline substitution at the point of call; however, even if this inline substitution is omitted, the other rules for inline functions specified in this section shall still be respected.
        (цитируется по N4659)

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

        > Вообще то в языке С++ компайл тайм вещи передаются в < >, а рантайм вещи передаются в ( ), про sizeof не нужно заикаться, т.к. это C

        Про sizeof я ни слова не сказал. Что такое эти «вещи» которые «компайл тайм»?
        И какое отношение имеет то, что сейчас «передаются», к тому, что использование <> крайне диверсионно для синтаксиса?

        > Значит вы не умеете ими пользоваться, они крайне удобны

        Когда подавляющее большинство не умеет, это уже проблемы языка. Если вам «крайне удобны», и вы в состоянии на языке шаблонов аккуратно разобраться с определением нужного через каскады всяких enable_if, или продраться через простыни в 7000 строк сообщений об ошибке при неправильном подключении boost.spirit (реальный случай у меня, я честно подсчитал строки) — ну, вам повезло. В основном же на них плюются и стараются минимально использовать.

        > Нет никакого другого способа разрешить компилятору хоть что то оптимизировать. Нет более хорошего пути чем введение УБ

        1. >95% кода практически любой программы не требуют оптимизаций, которым нужен именно такой UdB, который не виден из контекста данной строки и двух по соседству.
        Места, где это действительно нужно, можно было бы оградить пометками «вот тут мы разрешаем многое, будьте внимательнее».
        2. Большинство современных применений UdB в компиляторах — это просто злоупотребление возможностями, которые даёт стандарт, а порой и подкрутка стандарта под себя ради 2% в бенчмарках (как по приведенной мной ссылке).

        > Кому нахрен нужны именованные конструкторы? Функций не хватает?

        Которые конструируют объект? Да, не хватает.
        «Кому нужны» — это вы расскажите, например, тем, кто нарвался, что vector<int> x{5} создаёт 5-элементый набор нулей, а vector<int> x{5L} создаёт 1-элементный набор из числа 5 (ещё и урезав long до int без предупреждения). Это самый банальный вариант, а бывают и интереснее, которые народ неделями ловит. (В данном случае, кстати, на винде с LLP64 проблем не будет — а при переносе на Unix взорвётся. Ещё одна радость.)
        (Можно закостылить через фиктивный параметр конструктора, но почему не напрямую через имя?)

        > Очень простые принципы нахождения подходящей функции — наиболее подходящая.

        Да-да-да. С подстановкой неожиданных типов через неявные конверсии.

        > Сложности возникают только когда вы наворотили нечто очень странное в коде. Проблема тогда не в принципе, а в коде.

        Зависимость от чужого кода, где уже сотворено чёрт-те что и менять не получится — совершенно типовая ситуация. Странно, что вы с этим, судя по сообщениям, не сталкивались.

      • 0xd34df00d
        /#24000355 / +3

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

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


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

        Лол, нет. Языки отлично можно оптимизировать и без UB.


        Какой простор для оптимизации, кстати, открывает упомянутое мной выше UB с констекспром?

        • mayorovp
          /#24000513

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


          Хотя я бы на месте разработчиков стандарта добавил примитивы для constexpr-отладочного вывода вместо того чтобы разрешать constexpr иногда переставать быть constexpr.

          • 0xd34df00d
            /#24002575 / +2

            Я даже с этой подсказкой не понял, зачем добавлять это как UB.

    • dmitrmax
      /#24001577

      struct {
        int a;
        int b;
      } moo;

      А как тебе такое, Илон Маск?

      • netch80
        /#24001617

        Да, я потом подумал, что надо бы привести это как пример тоже. Но сами подобные безымянные типы — фича не сильно важная.
        И при желании и её можно было бы реализовать без ';' (я как раз этого не хочу, но принципиальная возможность налицо).

  14. wavebvg
    /#23999207

    Когда в одной статье написано, что не нравится "Точка с запятой;" и "Медленная сборка", то у меня возникает логический диссонанс. Обыватели тоже цвет записывают скрытых вещей в достоинства/недостатки, но чтобы этот вопрос обсуждать...

    • AtariSMN82
      /#23999217 / -3

      Точка с запятой;

      Да, это смешно и больше связано с моими фантазиями о каком-нибудь быстром питоне

      Медленная сборка

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

      • technic93
        /#23999341 / +2

        Компилировать в несколько потоков можно уже сейчас

      • Koval97
        /#23999453

        Модули в C++ существуют ещё задолго до появления первого компилятора для него, и называется это раздельная компиляция.

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

        • wavebvg
          /#23999901 / +2

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

          Тем более использовать перевод каретки как разделитель выглядит странно и лишает возможности форматировать код так, как хочется (даже несмотря на то, что любая ДОС в терминале выполняет команды по кнопке "ввод").

  15. cr0nk
    /#23999455 / +1

    Когда Страуструпа неустраивал C он написал C++. Предлагаю автору не скромничать и написать свой C+++ с модулями и системами сборки.

    • AtariSMN82
      /#24001803 / -1

      Зачем чё-то писать, когда есть язык Brat:
      p "hello world"
      Функция print в одну букву P, вы только вдумайтесь

  16. Koval97
    /#23999461 / +2

    Перефразируя всемизвестную цитату:

    "С++ сожет показаться ужасным языком программирования, если вы не пытаетесь написать что-то серьёзное на чем-то ещё".

    Если тебе не нравится язык программирования, то просто не пиши на нем. Пиши на том, что нравится, публикуй кейсы, а не кликбейтные заголовки!

    • netch80
      /#23999883 / +4

      > «С++ сожет показаться ужасным языком программирования, если вы не пытаетесь написать что-то серьёзное на чем-то ещё».

      В результате основная часть «энтерпрайза» ушла много лет назад на Java, C#, математики — только подключают C++ код (а местами даже Fortran) через Python или R, почти все девопс-тулзы написаны на Go, и так далее.

      Да, бо́льшая часть причин этого — неумение/нежелание морочиться с ручным управлением памятью (одно из самых неудобных и чреватых скрытыми ошибками), но часть аргументов автора статьи даёт вклад и в эти миграции.

      • dmitrmax
        /#24001605

        Энтерпрайз ушел в Java и C# по большей части из-за того, что там стандартная библиотека включает в себя больше нужных вещей. До сих пор в плюсах нету стандартных классов для работы с networking, включая высокоуровневые протоколы, gui и так далее. А в те времена, когда энтерпрайз уходил на Java, в std не было даже thread'ов.

        • netch80
          /#24001633

          > Энтерпрайз ушел в Java и C# по большей части из-за того, что там стандартная библиотека включает в себя больше нужных вещей. До сих пор в плюсах нету стандартных классов для работы с networking, включая высокоуровневые протоколы, gui и так далее.

          Ну и сейчас многих вещей нет в базовой поставке Java, но есть популярные библиотеки, которые использует каждый второй (не будем вспоминать log4j, но вот хотя бы Guava библиотеки). Аналогично в Python есть всякие http.client, но все ленятся и используют requests. Тут наверняка быстро возникло бы что-то аналогичное. И для C++ есть много библиотек и фреймворков, и что, кому-то сильно плохо, что Qt не в стандарте?
          В общем, сомнительный тезис.

          > А в те времена, когда энтерпрайз уходил на Java, в std не было даже thread'ов.

          Ну да, не было. Но переносимые библиотеки — врапперы вокруг штатных средств ОС — уже были, а уж под конкретный API мог писать каждый.

          • dmitrmax
            /#24001875 / +4

            По сути, только с распространением github + cmake, использование внешних библиотек стало возможным почти так же легко, как и в python можно зацепить pip install'ом. И что там в Java модно? mawen? А раньше это надо было хорошенечко потрахаться, чтобы собрать либу под все нужные платформы, в рилизе, в дебаге и так далее. Помню openssl собирался перловым скриптом, и под винду надо было поставить перл для этого. И прочие, и прочие "ништяки". А потом выходит новая версия либы и опять всё заново.

            В общем по сравнению с тем, чтобы просто взять Java, это было серьезно сложнее.

  17. Ryppka
    /#23999781 / +1

    В старой сишке обходились записью type(val) для преобразования типов

    Серьезно?

  18. MixonK
    /#24001771 / +1

    В старой сишке обходились записью type(val) для преобразования типов...

    С python перепутали видимо. Должно быть имели в виду:

    (type)val

  19. 0x4eadac43
    /#24004099

    Пара мыслей про боль автора с header-only библиотеками.

    Если код header-only библиотеки не меняется часто, то ускорить компиляцию поможет использование precompiled headers. В CMake, например, есть поддержка на уровне таргетов.

    Вообще, если в своем проекте создавать header-only библиотеки, затем постоянно что-то в них изменять и жаловаться на долгую компиляцию - сам себе злой буратино. Библиотеки делают для распространения кода, чтобы ими пользовались другие авторы, проекты. В таком сценарии, обычно, обновление библиотеки происходит между релизами. Соответственно, проблему постоянных долгих сборок можно избежать, правильно настроив сборочный процесс.

    • qw1
      /#24004571

      PCH кеширует только стадию разбора деклараций. Если h-файл состоит только из деклараций, то ускорение существенное. Если же в h-файле 10 мегабайт кода, который нужно сгенерить и положить в компилируемый сейчас бинарник, на каждой компиляции этот код будет генерироваться заново.

      • technic93
        /#24006809

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

      • 0x4eadac43
        /#24007081

        Я не углублялся в детали конкретных компиляторов, если честно. Я не подразумевал, что PCH заменит компиляцию. Каждый translation unit, в любом случае, будет перекомпилироваться, если файлы, от которых он зависит, будут изменены в процессе. Как я понял, PCH означает, что синтаксический разбор не надо будет заново производить, если не было изменений библиотеки, как минимум.

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

  20. AngelOfMors
    /#24006853

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

    Нигде нет идеала, мы ко всему подстраиваемся или меняем религию.

    Я думаю об алгоритме и т.д., а не о точке с запятой :D

    • AtariSMN82
      /#24006861

      Но точка с запятой не влияет на алгоритм, только на C++

      • AngelOfMors
        /#24007379

        Много места в ОЗУ занимает эта точка с запятой, фигурная скобка? Палец устал?

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

        Я после каждой закрывающей скобки блока (даже если там всего две строчки) делаю комментарий к чему она относится (на автомате) // if, // else, // while и т.д.

        Код читабельнее. И ни как не напрегает.

        А вот длинное reinterpret_cast. Согласен.