1. C++ / Говнокод #25982

    0

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    #include <cstdio>
    
    class tag;
    
    template<class>
    struct type { friend constexpr auto get(type); };
    
    template<class TKey, class TValue>
    struct set { friend constexpr auto get(TKey) { return TValue{}; } };
    
    void foo() {            // never called
      if constexpr(false) { // never true
        if (false) {        // never true
            constexpr auto call = [](auto value) { std::printf("called %d", value); };
            void(set<type<tag>, decltype(call)>{});
        }
      }
    }
    
    int main() {
      get(type<tag>{})(42); // prints called 42
    }

    https://twitter.com/krisjusiak/status/1186363017329594368
    Какой C++20 )))

    Запостил: j123123, 21 Октября 2019

    Комментарии (68) RSS

    • Попробуем разобраться. Обычно, когда пытаешься объяснить, сам начинаешь понимать

      #include <cstdio>
      
      class tag; // объявляем класс
      // Класс без тела? Часто ли нужны такие классы?
      // НАХУЯ может понадобиться класс, который содержит НИХУЯ и делает НИХУЯ??
      // ну да похуй, идём дальше
      
      template<class> // шоблонная структура, обычное дело
      // а нет, погодите, 
      // шоблонный тип нигде не используется
      // НАХУЯ тогда шаблон??
      // не понятно
      struct type { 
         friend constexpr auto get(type); 
         // метод без реализации (ну а нахуй она нужна?),
         // принимающий аргумент типа как и сама структура
         // возвращает хуй знает что. Как вообще конпелятор понял какой тип вернуть?
      };
      
      template<class TKey, class TValue> // обычный шаблон, тут ничего необычного
      // хотя постойте....
      // а ну да, пока всё ОК
      struct set {
          friend constexpr auto get(TKey) { return TValue{}; }
          // тут хотя бы понятно, что возвращаемый тип - TValue
      }; // эта структура понятная
      
      void foo() {            // never called
        if constexpr(false) { // never true
          if (false) {        // never true
              // лямбда, принимающая инт (судя по %d)
              constexpr auto call = [](auto value) { std::printf("called %d", value); };
              // создаём экземпляр класса, где
              // TKey = type<tag> (помним, что tag - это нихуя, type - обёртка над нихуя, не используящая это нихуя)
              // TValue = function<void(int)>, я ж не ошибся?
              void(set<type<tag>, decltype(call)>{});
              // приводим к воиду, кажется
              // а может это объявление функции?
          }
        }
      }
      
      int main() {
        // вызываем функцию get
        // но у нас нет ни функции get, ни класса get
        // чо за хуйня?
        get(type<tag>{})(42); // prints called 42
      }


      но нет
      Ответить
      • Ключевое слово friend же позволяет вызывать метод без экземпляра объекта.
        Когда компилятор увидит get, он сначала будет искать функции, а потом, если не найдёт, будет искать подходящих френдов.
        Ответить
        • Спасибо.

          // вызовется функция type::get или set::get?
          get(type<tag>{})(42);
          Ответить
          • А ну так ебать. Если вызовется set::get, то это всё объясняет. Вызов неинициализированной функции - UB.
            Ответить
            • Остаётся понять какого хуя конпелятор раскрыл TValue в лямбду. Только потому, что кто-то в одном месте заиспользовал её.
              Ответить
              • 1. Тут всё так же, как с перегрузкой обычных функций: компилятор ищет реализацию с максимально близкой сигнатурой.

                2. Но у нас нет обычных функций (и даже обычных френдов), у нас только шаблоны.

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

                4. В блоке, который never called, создаётся одна из конкретных реализаций шаблона. Она помещается в объектный файл в секцию кода, её видит линкер.

                5. О, чудо! Замангленное имя той фигни, которая создалась в блоке "never called" (а именно френд get класса set<type<tag>, decltype(call)>{}), наиболее близко подходит под сигнатуру требуемой функции.
                Ответить
                • Данила Багров
                  Ответить
                  • С++ - это удобный прафисианальный язык программирования. А кто не понял, как он работает, тот просто неосилятор.
                    Ответить
                  • Да, и у опытных крестоблядей на стуле лежит лист асбеста, а рядом стоит ведро воды. С ними можно использовать C++ почти без риска.
                    Ответить
                    • А скажите мне, обычной макаке, а нельзя не стрелять себе в ногу?
                      Ну просто писать простой предсказуемый код на С++ и течь?

                      Или обязательно нужно пройти все круги ада выбора перегруженной функции и остановиться на случайной лямбде?
                      Ответить
                      • Признаюсь: я тоже макака. Могу предложить писать, как Царь: "на сишечке с классами", избегая перегрузок и крестошаблонов.
                        Ответить
                        • Шаблоны могут быть вполне вменяемые, если не плодить в них сложную логику кмк

                          Перегрузки в С++ адские конено, я бы старался из избегать без необходимости

                          причем там есть И перегрузки И дефалтные значения (как в котлине)) , это наверное опасно если скрестить их с шаблонами

                          перегрузки + шаблоны = пздц
                          Ответить
                  • Сюжет "Брата-2" чрезвычайно прост, и вместе с тем буквально напичкан целым скопищем пропагандистских антидемократических мемов, которые вкупе с масштабом раскрутки ленты позволяют отнести её к программным фильмам диктатуры.

                    Главным героем фильма является Данила Багров — молодой человек неопределённого рода занятий и без определённого места жительства. Герой вырос в неполной семье в бедном и безымянном захолустье — многие посмотревшие фильм ассоциировали героя Бодрова с собой и хотели быть на него похожим — хотели строить так сказать свою жизнь по тем же клише. Важная деталь — в прошлом герой служил и воевал, однако не страдает никаким ПТСР, наоборот — служба в армии и война помогла стать ему настоящим мужчиной и научила чётко и ровно решать вопросы.

                    Враги главного героя — всякие коммерсы, которые занимаются непонятно чем, но главный враг Данилы — живёт за океаном и появляется только во втором фильме. Это — пиндосы. Именно заокеанские пиндосы являются средоточием мирового зла и проецируют все проблемы для Родины Данилы — к примеру, занимаются обманом наших спортсменов, а когда этот обман вскрывается — убивают их. Ещё пиндосы занимаются наркотрафиком, съёмками порнографии и угнетают негров.
                    Ответить
                    • Багров - абсолютно классический ват ник, появившийся еще до того, как появилось само слово ват ник.
                      Ответить
                • Иными словами ввиду отсутствия горичной был выебан конюх?

                  Причем конюх это лямбда, объявленная вообще внутри
                  Ответить
                • Ну про уб я прав же?
                  Ответить
                  • Ап
                    Ответить
                    • И тигры Боярского съели.
                      Ответить
                      • У тебя есть шанс выпить с нами увидеть живьем город, где обитает Боярский.

                        В сухую, безветренную погоду можно увидеть и самого Боярского
                        Ответить
                      • а бля, я думал ты икарус

                        обознался
                        Ответить
            • что такое "неинициализированая функция"?
              Ответить
              • std::function<void(int)> kuritsa;
                kuritsa(42);
                Ответить
                • попытаюсь угадать: курица это технически укокозатель на функцию (сиреч ее адрес)

                  если это автоматическая переменная, то там мусор (а если например глобальная то ноль).

                  Мусор это точно UB. Ноль -- хз, вероятнее всего экспешен как и попытка разыменовать нуль

                  (дисклаёмер: я не плюбсоеб)
                  Ответить
                  • Я бы просто инициализировал укокозатель нулём в коконструкторе, а при вызове проверял. Но так как это влияет на "пефомас", этого могли не сделать.
                    Ответить
                    • если можно что-то не делать -- си это не делает. Это важно, когда ты работаешь на CPU 20Mhz
                      Ответить
                      • 20 MHz? Это поди в турбо-режиме?

                        То ли дело первый писюк на 4 MHz.
                        Ответить
                        • На первом писюке на сях никто не писал.
                          Си пришли в районе 386, он уже был ближе к 20.
                          Ответить
                          • Писали. Хотя могу и врать: могли писать на более новых машинах под 8088. Кросскомпиляция!

                            386 уже были и на 33, и на 40. Кажется, 40 было пределом, быстрее были только 486.
                            Ответить
                            • а что писали например? xenix разве что.

                              большинство софта года до 1987-го под PC писалось на асме
                              Ответить
                      • Вообще забавно, чем руководствовались авторы Си и C++.

                        Кернигана и Ритчи я могу понять: они сидели за старым компом и выживали, как могли. Но зачем Страуструп сохранил все эти концепции (разве что сделал прототипы обязательными и отменил "автовывод" в int)?

                        И уж совсем чудовищным на этом фоне кажется boost: компилятор, экономящий на инициализации данных и на проверках границ массивов и сношающий конюха за неимением горничной, и в то же время библиотека необъятного размера, компилирующаяся за непредсказуемое время.
                        Ответить
                        • Страуструп руководствовался обратной совместимостью с сями.
                          Примерно всё вокруг него было написано на сях (и чуть чуть на фортране).
                          Примерно все вокруг него умели писать на си.

                          Если бы он сделал язык, не совместимый с си, его бы не поняли.

                          Boost, вероятно, думает что лучше 3 часа компилироваться, а потом экономить 3 секунды работы. В каком-то смысле их можно понять
                          Ответить
                          • А почему Страуструп не взял за основу Фортран?
                            Ответить
                            • Может быть потому, что он работал за Unix у которого API и все утилиты на си, и фортран считался несколько устаревшим тогда уже?

                              Ну вот представь ты пришел в контору, где все (не дай бог) на JavaScript. Миллионы строк кода и пара сотен программистов на JS.

                              И ты пишешь новый язык. Что ты возьмешь за основу?
                              Ответить
                              • "PHP".
                                Ответить
                                • Кстати да
                                  фейсбук и вконтакт именно так и сделали

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

                            Как и первая версия крестоговняного "компилятора" Cfront, которая по сути являлась говнопрепроцессором поверх сишки. А первые версии сишкокомпиляторов были кривыми говнотрансляторами в ассемблер PDP-11
                            Ответить
                            • Большинство языков и является кучей кривого говна.
                              У жабы нет генериков в компайл тайме ради совместимости с говном 1998 года, например
                              Ответить
                    • Мне нужен труп, Я выбрал Вас,
                      До скорой встречи!
                      Перфоманс
                      Ответить
                • Да ты просто раскуривать не умеешь!
                  Ответить
      • >class tag; // объявляем класс
        почему бы этому не быть форвард декларайшенам?
        Ответить
      • > Обычно, когда пытаешься объяснить, сам начинаешь понимать
        :))))))))))
        Есть такое.
        Ответить
      • https://habr.com/ru/post/472780/
        Ответить
        • Так почему вызов лямбды вызвал конкретную лямбду с printf-ом?
          Ответить
          • Всё очень смешно:
            https://gcc.godbolt.org/z/U-fv8o

            1. Тело функции foo состоит из nop, поскольку там if constexpr(false).
            2. Но эта пустая функция на этапе компиляции создаёт специализацию шаблона set<type<tag>, decltype(call)>.
            3. Её френд по имени get (в ассемблерном выхлопе метка _ZZ3foovENKUlT_E_clIiEEDaS_) возвращает экземпляр класса, переданного во втором аргументе (т. е. создаёт экземпляр decltype(call)).
            4. В свою очередь decltype(call){}() в C++20 приводит к вызову лямбды (только без захвата контекста; замыкания через конструктор вызвать не получится).

            Осталось понять, почему вызвана функция _ZZ3foovENKUlT_E_clIiEEDaS_, она же set<type<tag>, decltype(call)>::get(type<tag>), чья специализация родилась в недрах foo. Тропинка к ней от вызова get(type<tag>{})(42) в main появилась благодаря SFINAE: это функция, максимально подходящая под сигнатуру get(type<tag>).

            Перед лямбдой, кстати, вызывается конструктор type<tag>{}, и он пустой.
            Ответить
    • Джей один два три один два три, пойдёшь с нами в бар?
      Ответить
    • g++ 9.2.1:

      test.cpp:6:45: warning: friend declaration ‘constexpr auto get(type< <template-parameter-1-1> >)’ declares a non-template function [-Wnon-template-friend]
          6 | struct type { friend constexpr auto get(type); };
            |                                             ^
      test.cpp:6:45: note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here) 
      test.cpp: In instantiation of ‘constexpr auto get(type<tag>)’:
      test.cpp:21:18:   required from here
      test.cpp:9:62: error: use of deleted function ‘foo()::<lambda(auto:1)>::<lambda>()’
          9 | struct set { friend constexpr auto get(TKey) { return TValue{}; } };
            |                                                              ^
      test.cpp:14:32: note: a lambda closure type has a deleted default constructor
         14 |         constexpr auto call = [](auto value) { std::printf("called %d", value); };
            |                                ^
      test.cpp: In function ‘int main()’:
      test.cpp:21:6: error: void value not ignored as it ought to be
         21 |   get(type<tag>{})(42); // prints called 42
            |   ~~~^~~~~~~~~~~~~


      Хотя всё равно непонятно, какого чёрта он пытается использовать эту лямбду внутри foo.
      Ответить
      • Действительно, у лямбды до C++20, если её использовать в качестве класса, конструктор помечен как deleted. Я не знаю, как это исправить.

        Переписал без лямбды:
        #include <cstdio>
        
        class tag;
        
        template<class>
        struct type { friend constexpr auto get(type); };
        
        template<class TKey, class TValue>
        struct set { friend constexpr auto get(TKey) { return TValue{}; } };
        
        struct pituh { auto operator ()(auto value) { std::printf("called %d", value); } };
        
        void foo() {            // never called
          if constexpr(false) { // never true
            if (false) {        // never true
                constexpr auto call = pituh{};
                void(set<type<tag>, decltype(call)>{});
            }
          }
        }
        
        int main() {
          get(type<tag>{})(42); // prints called 42
        }


        https://ideone.com/CKQgWG

        Всё равно лезет в блок, который внутри if (false), который внутри if constexpr(false), который внутри void foo(), которая никогда не вызывается.
        Ответить
      • Вот корректная версия, не требующая C++20:
        #include <cstdio>
        
        class tag;
        
        template<class>
        struct type { friend constexpr auto get(type); };
        
        template<class TKey, class TValue>
        struct set { friend constexpr auto get(TKey) { return TValue{}; } };
        
        void foo()
        {
          if constexpr(false) {
            if(false) {
                    
              class pituh
              {
                public: 
                inline /*constexpr */ void operator()(int value) const {
                  printf("called %d", value);
                }
                private: 
                static inline void __invoke(int value) {
                  printf("called %d", value);
                }
              };
              
              constexpr const auto call = pituh{};
              void(set<type<tag>, decltype(call)>{});
            }
          }
        }
        
        int main()
        {
          get(type<tag>{})(42);
        }
        Ответить
      • От лямбды ещё можно наследоваться:
              constexpr auto lambda = [](auto value) { std::printf("called %d", value); };
        
              class pituh: public decltype(lambda) {
                public: 
                pituh() {};
              };
        
              void(set<type<tag>, pituh>{});

        Но в компиляторах до C++20 выкинет с сообщением об ошибке:
        e.cpp:20:17: error: use of deleted function 'foo()::<lambda(auto:1)>::<lambda>()'
                 pituh() {};
                         ^

        Конструктор с атрибутом deleted перекрыть не получается. Либо это невозможно, либо я слишком анскилльный.
        Ответить
        • Починил:
          #include <cstdio>
          
          class tag;
          
          template<class>
          struct type { friend constexpr auto get(type); };
          
          template<class TKey, class TValue>
          struct set { friend constexpr auto get(TKey) { return TValue::bar(); } };
          
          void foo()
          {
            if constexpr(false) {
              if(false) {
                      
                constexpr auto lambda = [](auto value) { std::printf("called %d", value); };
          
                class pituh: public decltype(lambda) {
                  public: 
                  static decltype(lambda) bar() {return lambda;};
                };
          
                set<type<tag>, pituh>{};
              }
            }
          }
          
          int main()
          {
            get(type<tag>{})(42);
          }


          Заменил вызов конструктора вызовом статического метода. Компилируется в C++17 (если бы не if constexpr, можно было бы ещё понизить версию стандарта).
          Ответить
          • Если убрать if constexpr, оставив только if(false), то компилируется в C++14 и работает так же. Даже с -O2: оптимизация выкидывает все вызовы промежуточных функций, перенося вызов printf прямо в main.
            Ответить
    • Разбор оригинального кода (требует поддержки C++2a из-за потребности в конструкторе лямбды, который недоступен даже в C++17):
      https://cppinsights.io/s/6a95dc50
      Ответить
    • Кстати, что-то я подзабыл, компайлтайм переменные на основе подобной хуеты с френдами пилили?
      Ответить
      • У меня про это был говнокод https://govnokod.ru/24542

        Ну и вот еще https://habr.com/ru/post/268141/ https://stackoverflow.com/questions/44267673/is-stateful-metaprogramming-ill-formed-yet
        Ответить

    Добавить комментарий