Зачем нам использовать Modern C++, особенно старших версий? Дурацкий вопрос, скажут сектанты, фанаты, фанатики, адепты. Оно же необходимо для большей выразительности, читабельности и ускорения всех возможных процессов, включая метаболизм; и еще для экономии времени. Что-то в этом есть, а кое-чего нет. Давайте рассмотрим пример из жизни.

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

#include <vector>

int main() {
    std::vector<int> vector;
    vector.push_back(0);
    vector.push_back(0);
    vector.push_back(0);
//здесь давайте на секунду представим себе, что содержимое вектора заранее неизвестно и он вполне может быть пустым
  int x=0;
    for(auto& n:vector){
      x++;
        //Сделай что-нибудь!
    }
    return 0;
}

Написал, сладко потянулся и отправил в билд.

К слову, билд штука непростая, состоит из многих этапов и занимает примерно час+.

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

Стали разбираться.

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

Оказывается, в range-based for loop нельзя затащить локальную переменную, как в обычном цикле. Ну то есть можно, но об этом ниже.

Получив письмо счастья, автор сел разбираться.

И пришел к выводу, что есть несколько способов решить проблему:

1. приказать линтеру заткнуться и не вякать (хаха, смешно; то есть это осуществимо, но не принято).

2. после цикла эту несчастную переменную аннулировать, например выражением (void)x или что-нибудь в этом духе.

3. написать классический цикл, задать локальную переменную - как деды завещали.

4. перейти на старшую версию c++ (хахаха, гомерический хохот, долгие, несмолкающие аплодисменты)

5. или попытаться добавить локальную переменную в range-based for loop, как-то так:

for(int x=0; auto& n:vector)
{
  x++;
  //Сделай что-нибудь!
}

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

Лирическое отступление

Итого, что получается?

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

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

ссылки:

https://en.cppreference.com/w/cpp/compiler_support/11.html

https://en.cppreference.com/w/cpp/compiler_support/14.html

https://en.cppreference.com/w/cpp/compiler_support/17.html

https://en.cppreference.com/w/cpp/compiler_support/20.html

https://en.cppreference.com/w/cpp/compiler_support/23.html

https://en.cppreference.com/w/cpp/compiler_support/26.html

Обратили внимание? Даже ведущие компиляторы, уже начиная со стандарта 2017 (а по факту даже с 2011) не гарантируют полную совместимость со стандартом как по самому языку, так и по стандартной библиотеке. И дальше расхождений все больше - в 2020 даже gcc, до этого бывший огурцом, начал уставать.

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

Попросту говоря, если вы пишете код под стандарт 20хх под конкретный компилятор - не факт, что вы сможете скомпилировать его под другим компилятором! И это еще до того, как мы начала говорить о различиях в железе.


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

Например, стандарт 26 целиком не реализует вообще никто. Даже наполовину не. Даже стандарт 23 никто целиком не реализует.
А ведь планируется уже стандарт 29.
Тут уместно задать вопрос: а зачем вообще плодить стандарты которые реализуются через пень-колоду?
Но это наверно риторический вопрос, который невозможно расслышать через бой бубнов.

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

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

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

Успехов всем нам.

Вернемся в исходную историю.

Как же наш герой справился с проблемой?

Во общем, эту проблему парень решил, неважно как (как-как: перешел на стандарт 2023! Хахаха, люди задыхаются от смеха и сползают с кресел!) Просто написал нормальный классический цикл.

Я бы хотел заострить внимание на другом.

Человек мог написать этот код по дедовским рецептам и все прокатило бы.

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

Затем минут 15 он выяснял где накосячил и как чинить.

Еще минут 30 он чинил - потому что мало починить, желательно еще локально скомпилировать и запустить, чтобы убедиться что все в порядке, а это занимает время.

И потом еще раз запустить билд.

Итого: за следование моде он заплатил более чем часом работы.

Finale

Выводы каждый пусть делает сам.

P.S. Этот текст был создан без использования БЯМ.

Комментарии (15)


  1. BorisU
    26.08.2025 10:50

    у вас кривой линтер и вы не умеете запускать его без компиляции всего. Язык-то то тут причем?


  1. Abstraction
    26.08.2025 10:50

    В язык затащили новую фичку. Но она оказалась малость недоделанной

    Что недоделанного в самой фиче? Она работает как и обещалось, возможности объявлять что угодно в области видимости цикла нет, её и раньше не было.

    Я бы сказал "схрена ли такое правило линтера", но и правило разумное: если вы никак не используете x вне цикла, то что он вообще делает? Если это хитрое преобразование коллекции с аккумулятором, то правило линтера намекает вынести его в отдельную функцию (или воспользоваться std::transform):

    static void TransformData(std::vector<int>* data){
    int x = 0;
    for(int& elem : *data){
    elem = x += elem;
    }
    }
    (Если линтер и в этом случае считает код нарушением, то ИМХО лучше написать явное исключение, чем if(data->empty()) return;)

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


  1. sergey_prokofiev
    26.08.2025 10:50

    Сначала была одна крайность: C++0X рожали лет 10 и так и не сумели выпустить в 0X годах.

    Затем другая крайность: по небольшому стандарту, но каждые 2 года.

    Наверное следующая итерация будет "вменяемый баланс".


    1. eao197
      26.08.2025 10:50

      Затем другая крайность: по небольшому стандарту, но каждые 2 года.

      Назвать C++20 небольшим стандартом как-то сложновато.
      Да и грядущий C++26 тоже маленьким не назовешь...


  1. apevzner
    26.08.2025 10:50

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

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


    1. dayman092
      26.08.2025 10:50

      Да, можно прогнать линтер локально на проектах уровня хелловорлд+, но даже с 10-ти летним аосп это не выйдет и я не знаю, как там прогнать линтер отдельно. И зачем тратить время на поиски этой информации?

      По-моему с++ скатился где-то на std::chrono и полностью превратися в Г при появлении корутин, все их пишут, куча реализаций, прироста с CSP почти никакого, проекты, в итоге, становятся неподдерживаемыми.

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


  1. eao197
    26.08.2025 10:50

    Даже ведущие компиляторы, уже начиная со стандарта 2017 (а по факту даже с 2011) не гарантируют полную совместимость со стандартом как по самому языку, так и по стандартной библиотеке.

    Это моя реальность начиная с 1990-х годов, еще даже до принятия первого стандарта -- у каждого компилятора было свое подмножество (а в бородатых 90-х, порой, и нестандартное надмножество) C++.

    Таков путь.

    Прямое следствие того, что есть отдельно стандарт и есть несколько независимых вендоров компиляторов.

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

    Жить с этим можно. А страдают от такого, в основном, разработчики кросс-платформенных библиотек и кросс-платформенных приложений. Если же проект живет в рамках одной платформы и одного компилятора (а таких, как оказалось, немало), то и проблем особых нет.


  1. Cheater
    26.08.2025 10:50

    переменную аннулировать, например выражением (void)x

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


  1. NeoCode
    26.08.2025 10:50

    Уверен в том, что С++ нужно прекратить развивать тем способом как это делается сейчас (добавлением новых фич с сохранением обратной совместимости со старыми). Нужно ввести версии языка, и в новой версии просто удалить всю ту фигню которую нагородили эволюционно, и добавить новые фичи вообще не оглядываясь на обратную совместимость, а думая лишь об их логичности, удобстве, красоте. Чтобы компилятор оличал исходники разных версий в одном проекте, в начале файла писать #pragma version. Если прагмы нет - значит "старая" (т.е. текущая) версия.


    1. Videoman
      26.08.2025 10:50

      Звучит заманчиво, самому бы хотелось решения с помощью подобного варианта, но возникают вопросы. Допустим у нас компилятор с помощью опций или директив поддерживает старую и новую версию С++. Мы можем писать код полностью на новой версии или на старой версии - ура! Но если мы хотим поддерживать несколько версий одновременно, значит мы планируем какой-то постепенный переход. А как в этой ситуации делать interop ? ABI сломано, одни и те же конструкции теперь могут означать совсем разное. Через "С" ?


    1. Jijiki
      26.08.2025 10:50

      на первый взгляд звучит уверенно и вроде логично, но какой ценой ... что-то там, С++ не плохой по моим прикидкам


  1. XViivi
    26.08.2025 10:50

    [[maybe_unused]] добавили в C++17. Даже до этого, если нет согласия с линтером и есть достаточная уверенность, что он брешет, вариант с (void) или любой другой подобной глушилкой был бы не так плох.

    Собственно, я впринципе с точки зрения линтера не особо понимаю, в чём проблема, если переменная не использовалась в одной из веток — важнее же чтобы использовалась хотя бы в одной, разве нет?


  1. BombaBomba
    26.08.2025 10:50

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


  1. pavlushk0
    26.08.2025 10:50

    Тейк про линтер непонятен, у вас clang daemon в ci или к иде не прикручен? Новые стандарты поддержаны неполно, но от этого не становятся бесполезными. Нишевого проходняка много, конечно, но концепты, корутины и модули это отлично, start lifetime as тоже супер, рефлексия вот подошла, всякого полно. А про то что язык большой это уже данность, его никто целиком не знает, дпже если делает вид что знает не верьте) P1787 тому докозательство.


  1. arteast
    26.08.2025 10:50

    Судя по всему, x - это индекс элемента в коллекции. Поэтому вариант номер 6 - выразить это явным образом: использовать enumerate из range-v3 или `boost::adaptors::indexed из boost-range + structured bind.