Кресты / Говнокод #25982 Ссылка на оригинал

0

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  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 j123123, (Updated )

Комментарии (71) 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" чрезвычайно прост, и вместе с тем буквально напичкан целым скопищем пропагандистских антидемократических мемов, которые вкупе с масштабом раскрутки ленты позволяют отнести её к программным фильмам диктатуры.

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

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

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

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

                      обознался
                      Ответить
          • что такое "неинициализированая функция"?
            Ответить
              • попытаюсь угадать: курица это технически укокозатель на функцию (сиреч ее адрес)

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

                Мусор это точно 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.

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

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

                          Как и первая версия крестоговняного "компилятора" Cfront, которая по сути являлась говнопрепроцессором поверх сишки. А первые версии сишкокомпиляторов были кривыми говнотрансляторами в ассемблер PDP-11
                          Ответить
                          • Большинство языков и является кучей кривого говна.
                            У жабы нет генериков в компайл тайме ради совместимости с говном 1998 года, например
                            Ответить
                  • Мне нужен труп, Я выбрал Вас,
                    До скорой встречи!
                    Перфоманс
                    Ответить
    • >class tag; // объявляем класс
      почему бы этому не быть форвард декларайшенам?
      Ответить
    • > Обычно, когда пытаешься объяснить, сам начинаешь понимать
      :))))))))))
      Есть такое.
      Ответить
      • Так почему вызов лямбды вызвал конкретную лямбду с 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>{}, и он пустой.
          Ответить
          • Ня самом деле всё просто: в момент определения foo() создаётся специализация set<type<tag>, decltype(call)>, эта специализация через friend injection создаёт перед foo() специализацию get(type<tag>) -> decltype(call), специализация type<tag> точня так же инжектит объявление get(type<tag>), чтобы get() можня было няйти через ADL. В main() на вызове get(type<tag>) срабатывает ADL, няходится объявление из type<tag>, к этому объявлению няходится инжектнутое определение. Ну а передача лямбды — совсем примитивное дело.

            class tag же нужен для того, чтобы объявление get(type<tag>) было т.н. "templated entity" ([temp.pre]/8) — иняче определение get() из set<> ня сматчится с объявлением.
            Ответить
  • Джей один два три один два три, пойдёшь с нами в бар?
    Ответить
  • 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
    Ответить
  • Кстати, что-то я подзабыл, компайлтайм переменные на основе подобной хуеты с френдами пилили?
    Ответить

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

Я, guest, находясь в здравом уме и твердой памяти, торжественно заявляю:

    А не использовать ли нам bbcode?


    8