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

    −127

    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
    23. 23
    24. 24
    25. 25
    using System;
    
    public interface IFoo
    {
        void WhoAmI<T>(T t);
    }
    
    public class Foo : IFoo
    {
        public void WhoAmI<T>(T t)
        {
            Console.WriteLine(t.GetType().FullName);
        }
    }
    
    class pituh
    {
        public static int Main(string[] args)
        {
            var f = new Foo() as IFoo;
            f.WhoAmI("asd");
            f.WhoAmI(10);
            return 0;
        }
    }

    Решите задачку.

    есть некий код на C#

    using System;

    public interface IFoo
    {
    void WhoAmI<T>(T t);
    }

    public class Foo : IFoo
    {
    public void WhoAmI<T>(T t)
    {
    Console.WriteLine(t.GetType().FullName);
    }
    }

    class pituh
    {
    public static int Main(string[] args)
    {
    var f = new Foo() as IFoo;
    f.WhoAmI("asd");
    f.WhoAmI(10);
    return 0;
    }
    }


    напишите его эквивалент на С++

    Запостил: ASD_77, 28 Марта 2017

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

    • а ты не знаешь как имена классов анманглить?
      Ответить
      • не. я давно не читал правила как "манглить" их. тем более что у gcc &msvc свои правила.
        Ответить
        • Для gcc/clang есть abi::__cxa_demangle. Думаю, что мелкомягкие тоже что-то выдумали (явно не как у людей).
          Иными словами, парочка #if с проверкой на компилятор/окружение должны решить вопрос c кросс-компиляторным demangle. Я почти уверен, что на GitHub (or whatever) уже можно найти рабочий код.

          P.S. abi::__cxa_demangle дико бесит, что не constexpr. А ведь тип вполне себе известен на этапе компиляции. Такое ограничение не дает делать constexpr там, где надо получить имя типа...
          Ответить
          • А просто имя в тайпинфо констэкспр, кстати?
            Ответить
            • Тоже нет. Хотя имена типов можно вполне себе вкомпилить в бинарник как и остальные строки. Да, не всех, а только нужных.

              Оператор, который генерирует литерал по имени типа. Что-то вида typeid.
              template <typename T>
              void foo() {
                  printf("%s\n", type_str(T));
              }

              Ответить
              • Можно для него даже слово typename зареюзать, чтобы новый кейворд не вводить.
                Ответить
                • # чтобы новый кейворд не вводить

                  Скажи это комитету
                  Ответить
    • #include <system>
      
      class IFoo
      {
      public:
          virtual void WhoAmI(void* t) = 0;
      };
      
      class Foo : public IFoo
      {
      public:
          void WhoAmI(void* t)
          {
              Console.WriteLine("void*");
          }
      };
      
      int main() {
          IFoo *f = new Foo;
          f.WhoAmI((void*)"asd");
          f.WhoAmI((void*)10);
          return 0;
      }


      а ты часом не бароп?
      Ответить
      • вы сознательно ушли тут от темплейта. а в этом суть вопроса :)
        Ответить
        • Это шуточка была. А если серьезно, то в плюсах аналогичное реализуемо с помощью boost::any и boost::demangle. И на сколько я понимаю, в сисярпе под копотом оно работает как раз примерно так.
          Ответить
          • в сисяпре оно работает как есть. т.к. виртуальная таблица хранит указатель на дженерик метод который переводиться в специализацию во время выполнения кода. а значит никакого кастинга делать не приходиться из-за того что используется тип генерика
            Ответить
            • А в C++ вообще на этапе компиляции известно, какая конкретная реализация шаблона выполняется, и поэтому тем более не приходится ничего ни к чему кастовать.
              Ответить
              • А особо умный конпелятор вообще весь этот код выкинет и заменит на вывод нужных строк...
                Ответить
                • а нука раскажи мне милый чел как это компилятор опеределит какая функция была вызванна особенно если она "virtual" что бы ее выкинуть :).
                  Ответить
                  • Да запросто, если создание объекта неподалёку находится (а оно тут как раз в соседней строчке с вызовом). Девиртуализацию никто не отменял.
                    Ответить
                    • ну в принципе да. но если прогнать через функцию тогда у компилятора облом будет, но он изначально скажет что виртуальные теплейтные функции идут в лес :)
                      Ответить
                      • > виртуальные теплейтные функции идут в лес
                        И правильно сделает.
                        Ответить
                    • показать все, что скрыто# Девиртуализацию никто не отменял.

                      Что бы отменить что то, надо решить что то.
                      Ответить
                  • Программа отрабатывает за конечное малое время, её результат постоянен и может быть вычислен на этапе компиляции. Теоретических ограничений нет.
                    Ответить
                    • не важно сколько оно проведет время ее результат определить не возможно т.к. у компилятора нет метода определить какой класс является владельцем интерфейса(ака абстрактного класа) а значит "задача оптимизации" результата не имеет и оптимизация прозведенна не может быть. кроме одного случая о котором написал bormand
                      Ответить
                      • Указанный код работает за небольшой промежуток времени, результат его выполнения не изменяется от запуска к запуску и может быть легко вычислен ещё на стадии написания программы. Нельзя доказать отсутствие компилятора, который проведёт эту оптимизацию.
                        Ответить
                        • >> Нельзя доказать отсутствие компилятора, который проведёт эту оптимизацию.

                          ну так скомпиляйте нам код. а мы посмотрим на него :)
                          Ответить
                          • Мой конпелятор из C# в C# вот что выдал:
                            class main
                            {
                                public static int Main(string[] args)
                                {
                                    System.Console.WriteLine("System.String");
                                    System.Console.WriteLine("System.Int32");
                                    return 0;
                                }
                            }
                            Ответить
                            • а теперь уберите var f = new Foo() as IFoo; и загоните классы и методы в либу.
                              Ответить
                              • Лучше вставить выбор дочернего класса в зависимости от пользовательского ввода.
                                Да, общий случай никто не предскажет и не запилит пацанский пирфоманс. Печально.
                                Ответить
                            • # Мой конпелятор из C# в C#

                              Покаж
                              Ответить
              • Собсно в этом и разница шаблона и генерика.
                Ибо генерик вообще не шаблон, это сахарок над Foo<Object>.
                В крестах же напротив, шаблонный метод виртуальным не сделать.
                Ответить
                • Асд77 выше утверждает, что там не касты в рантайме, а инстанцирование шаблонного метода в рантайме (типа сисярп в рантайме конпелирует конкретную реализацию, как jit). Это если я правильно его понял. Сам-то я не знаю и гуглить не хочу.
                  Ответить
                  • пусть тогда сделает статический полиморфизм в шарпике в виде перегрузки
                    int f(Collection<Integer> c) { return 1; }
                    int f(Collection<String> c) { return 2; }

                    раз две принципиально различные вещи для него должны быть сравнимы в категориях лучше-хуже
                    Ответить
                    • using System;
                      
                      namespace Rextester
                      {
                          public class Collection<T> {};
                          
                          public class Program
                          {
                              static int f(Collection<int> c) { return 1; }
                              static int f(Collection<string> c) { return 2; }
                              
                              public static void Main(string[] args)
                              {                      
                                  //Your code goes here
                                  Console.WriteLine(f((Collection<int>)null));
                                  Console.WriteLine(f((Collection<string>)null));
                              }
                          }
                      }


                      на балуйся.

                      1
                      2
                      Ответить
                      • ок, спасибо, почитал про разницу в генериках java vs c#

                        ну тогда другой пример - template specialization:
                        int f<T>(T c) { return 1; }
                        int f<>(string c) { return 2; }  // c# не может в <>, на стековерфлоу советуют их убрать и получить просто перегрузку
                        
                        int g<T>(T c) { return f(c); }
                        
                        call g((string)null);


                        если кококомпилятор c# сохраняет информацию о типах, и сам по себе язык обмазан сахаром в 10 слоев, что им помешало реализовать такую несложную вещь, как частичная/полная специализация?

                        ну или аналог крестушкового template <int N, int M> class Matrix()

                        раз уж так хочется сравнить несравнимое
                        интересно послушать
                        Ответить
                        • Специализация шаблонных функций в плюсах не нужна, т.к. есть перегрузка. Два способа сделать одно и то же - это скорее недостаток.
                          Ответить
                          • код, который я привел выше, в c# выберет шаблонную реализацию (возвратит 1), а не перегруженную
                            Ответить
                            • http://rextester.com/WWU87721
                              Ответить
                            • потому что в С# "они" могут сделать
                              if (c is string)
                              {
                              // do for string
                              }
                              else
                              {
                              // do for all others T
                              }
                              Ответить
                              • ну совсем одно и то же, ага!
                                такой dynamic cast даже в pl/sql есть, прости господи

                                а можно за O(0) всё таки - компилятор же знает о типе объекта, почему он не выбирает сразу правильную "перегрузку" из набора функций?
                                или про отсутствие type erasure маркетинговые презентации всё же немного подпёздывают?
                                Ответить
              • это бред - не говорят не люди не коты.

                там виртуальная функция для особо одаренных стоит что бы компелятор от с++ жидко обгадился когда начнет применять темплейты к фиртуальной функции
                Ответить
                • > метод который переводиться
                  > делать не приходиться
                  > это бред - не говорят не люди не коты.
                  > что бы компелятор
                  > там виртуальная функция для особо одаренных стоит что бы компелятор от с++ жидко обгадился когда начнет применять темплейты к фиртуальной функции
                  Да это же вореционный бот, который своим кобенаторным алгоритмом осознал C++ до возможности написания своего буста, но русский язык учил по комментариям на Ютубе.
                  Ответить
    • Утакот: https://wandbox.org/permlink/GEXKPeaaRRXQZrkz

      Static-C++ style. Ибо нефиг тут динамотой забавляться.
      Ответить
      • красивый пример, но там IFoo темплейт. А хотелось бы безтемплейтный интерфейс (абстрактный класс)

        похоже надо самому строить __vtbl и вызывать через номер метода типа __vtbl[0]
        Ответить
        • А если оставить шаблону статику, но вывести значение общего типа и пустить его в динамику?
          class IFoo {
          public:
              template <typename T>
              void WhoAmI(const T &t) const {
                  WhoAmI(typeid(t));
              }
          protected:
              virtual void WhoAmI(const std::type_info&) const = 0;
          };
          
          class Foo : public IFoo {
          public:
              virtual void WhoAmI(const std::type_info&) const {
                  // ...
              }
          };

          А если WhoAmI(const std::type_info&) захочет распечатать само значение, дать ему ссылку на IPrinter, которую создавать в WhoAmI(const T &t), пока тип и значение известны.
          class IPrinter {
          public:
              virtual void print() const = 0;
          };
          
          template <typename T>
          class Printer : public IPrinter {
          public:
              Printer(const T& data) : data(data) {}
              virtual void print() const {
                  std::cout << data;
              }
          private:
              const T& data;
          };
          Ответить
          • Ну вот, если переделывать пример, то так:
            https://wandbox.org/permlink/OPyDQMgGd2aKj8K0
            Ответить
            • Хороший подход, виртуальные методы лучше "прятать". Так больше контроля над тем, что делают дочерние классы.
              Ответить
              • особенно над теми, которые виртуальные методы в паблик выносят
                Ответить
                • Вынос базового приватного (да, они нормально работают и в "private") виртуального метода в public -- личная психологическая проблема автора дочернего класса.
                  Ответить
                  • я бы не сказал, что изнасилование детей - проблема насильника
                    Ответить
            • а зачем вообще через виртуальные методы и наследование? оО
              Ответить
              • ASD_77 этого зачем-то хотел:
                там виртуальная функция для особо одаренных стоит что бы компелятор от с++ жидко обгадился когда начнет применять темплейты к фиртуальной функции

                Может, специально придумывал искусственную задачу, где C++ напрямую не работает как C#.
                Ответить
                • хз, можно в хэдер вынести всего лишь:
                  struct TypeName {
                      template <typename T>
                      static void whoAmI(T &&) { _impl(typeid(T)); }
                  private:
                      static void _impl(const std::type_info &);
                  };

                  а остальное в цппшник. И не надо инстанцировать никакой хуеты
                  Ответить
                • ну да. в этом же и есть "задача" что бы обойти ограничение. Задача что бы мозги потренировать
                  Ответить
        • Ламер, что ты там строить собрался? Я тебе уже написал про boost::any и boost::core::demangle.
          Ответить
          • иди спать. (void*) я и без буста сделаю :)
            Ответить
            • ты уж если не знаешь, то и не пезди. any - это не просто войд*, а войд* + тайпинфо.
              Ответить
              • я просто рад :)
                Ответить
              • ок т.к. все абьюзят typeinfo в с++

                напишите код который будет работать
                using System;
                
                namespace Rextester
                {
                    public interface ICreator
                    {
                        T New<T>() where T : new();
                    }
                    
                    public class Foo : ICreator
                    {
                        public T New<T>() where T : new()
                        {
                            ////Console.WriteLine(typeof(T).Name);
                            return new T();
                        }
                    }
                    
                    public class Program
                    {
                        public static void Run(ICreator creator)
                        {
                            var newString = creator.New<Foo>();
                            var newpituh = creator.New<Program>();            
                        }
                        
                        public static void Main(string[] args)
                        {
                            Run(new Foo());
                        }
                    }
                }


                Да можеш заюзать Boost::any :)
                Ответить
                • Ламер, нельзя в плюсах функции в рантайме компилировать, хватит уже. И не нужно. В рантайме динамический полиморфизм, а шаблоны - в компайлтайме.
                  Ответить
                  • свали в тюбик - раз программить не научился. Во первых 1) никто не заставляет компилить в ++ это раз. во вторых с# написанн та тех же с++/c и прекрасно все умеет делать
                    Ответить
                • фабричный шаблонный метод, возвращающий инстанс класса? Ты, похоже, особая снежинка:
                  template <typename T, typename ... Args>
                  T *make(Args && ...args) {
                      return new T(std::forward<Args>(args)...);
                  }
                  Ответить
                  • Прикол в том, что он хочет это делать через интерфейс. Т.е. совокупить единорога и лепрекона virtual и template в одном методе. Ясно, что в плюсах это (по крайней мере в стандарте) это невозможно.

                    С другой стороны сделать то, что он хочет, довольно просто с помощью паттерна из моего первого примера (aka "curiously recurring template pattern")
                    Ответить
                    • > по крайней мере в стандарте
                      Если учесть, что темплейт разворачивается (инстанциируется) только компилятором, и в рантайме инстанциированный темплейт это полноценный обособленный тип (или полноценная обособленная функция), которому в целом поебать, был ли он шаблонным когда-то, и кто его собратья и есть ли они вообще, при этом нет смысла хранить (мета?) код исходного темплейта - рантайму ничего с этим кодом не сделать, он не может и не должен в jit компиляцию какую-то, и новый код из xml или из сети он не сгенерит на лету (и тем более не прооптимизирует, не разберется со SFINAE, не найдет частичные специализации и т.д.) - то виртуальный шаблонный метод это оксюморон. Независимо от стандарта.

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

                      Буквально недавно я генерил различные многомерные данные (статистика), которые беком должны были обернуться в 0-мерные (значение), 1-мерные (массив), 2-мерные (массив массивов), 3-мерные (...) структуры и отправиться на фронт для визуализации.

                      Что сделал жавоёб? Один тип для T, второй тип для List<T>, третий тип для List<List<T>>, четвертый для List<List<List<T>>>.
                      Помогли ему его интерфейсы с виртуальными генериками? Хуй там.
                      Ответить
                      • а ниче что когда компилятор компилить темлейт то он становиться конкретной специализацией. А если он стал специализацией то уже перестал быть темлейтом а значить "virtual" должен аплаиться. А компилятор не знает как :)
                        Ответить
                        • куда-куда аплаиться?
                          виртуальный означает, что потомок будет переопределять когда-нибудь с тем же контрактом (имя+параметры)
                          это когда-нибудь может возникнуть в другой длл спустя 10 лет
                          базовый класс не может предугадать как потомок будет инстанциировать этот метод
                          что он может сохранить для потомка в данном случае?

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

                          чтобы когда-нибудь jit компилятор смог в рантайме проинстанциировать эту метушню в валидные исполняемые инструкции

                          ну или смириться с тем, что <T> это просто сраный сахарок над Object, и контракт этой виртуальной функции именно что принимаем/возвращаем Object, ну или void * - и в этом случае вообще никаких оптимизаций (код предка уже скомпилирован в бинарном виде и неизменяем) и тем более специализаций, максимум, проверки компилятора о типе возвращенного объекта (ещё один сахарок)
                          Ответить
                          • почему копмилятор не подумал что референсить методы в таблице можно не только через целое положительное число. А например по кол-ву параметров или по sha256 от имени метода?
                            Ответить
                            • По какому количеству?
                              Я всё ещё не пойму

                              Каждый шаблонный метод в крестах раскрывается в свою имплементацию сразу, как только компилятор видит необходимость в этом.
                              https://wandbox.org/permlink/N9ysRa2YhsT0d2SH
                              Затем все эти инстанциированные методы лягут в obj файл каждый под своим именем и адресом, чтобы линкер смог их в других модулях подставить в случае нужды.
                              Нет нужды в инстансе от, например, <char> - никто никакой код не сгенерит, и даже не подумает.

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

                              Что именно ты собрался положить в ТВМ? Исходник шаблонного метода? Я об этом выше написал.

                              Т.к. спустя 10 лет я пишу свой тип MyAwesomeClass и инстанциирую им эту функцию, написанную кем-то 10 лет назад - какой код должен сгенерить компилятор за 10 лет до того, чтобы так заработало и потомком была найдена точка входа в подпрограмму foo::process<MyAwesomeClass>()?
                              Ответить
                              • давайте по порядку с самого начала.

                                1) проблема - С++ не разрешает использовать виртуальные темплейтные методы. Почему? потому что компилятор не может предугадать какие параметры темлейты могу быть использованны и поэтому не может сгенерировать __vtbl соответсвенно. А почему он не может это сделать? Ответ: потому что __vtbl распологает методы по порядку 0, 1, 2 в зависимости от того что в декларации класса написанно.

                                Но все это можно было бы избежать если бы методы в __vtbl референсились не по порядку как в декларации написанно а например по Hash code от имени функции, И тогда больше бы не было проблем с виртуальными темплейтными методами потому что "derived" класс всегда бы знал куда нужно положить метод в виртуальную таблицу.

                                т.е. не референсить по имени как делают dynamic языки а по hash - и все бы были довольны
                                Ответить
                                • Ты почитал что я написал выше?

                                  virtual void process<int> и virtual void process<double> это две разные подпрограммы.
                                  У них у каждой свои точки входа, у них у каждой свое тело, они даже вправе делать абсолютно своё, независимое друг от друга, и знать друг о друге не хотят.

                                  При этом никакой точки входа у virtual void process<T> нет, т.к. в принципе нет никакого исполняемого кода, который олицетворял собой "неинстанциированный шаблон". Нет никаких машинных инструкций, не сохраняется никакого интермидиед-результата (там компилятор максимум AST может построить в процессе первого этапа разбора, проверить синтаксис - и то старые вижуал студии хуй забивали на двухэтапный разбор шаблона, проверяли только второй этап, в момент подстановки конкретного типа). В единице выполнения (exe, dll) шаблона нет. Есть только инстансы с конкретными типами. Всё. У них нет никакой семейственности. В исполняемой программе в режиме release нет никакого реестра созданных ей типов, ничего такого. Всё байты, даже небо, даже аллах. Исполняемый код - машинные инструкции под конкретную архитектуру/ОС/процессор.

                                  В _vtbl должны лечь указатели на подпрограммы в качестве значения куда делать call/jmp.
                                  Для метода process<T> никакого указателя нет. Для инстансов методов process<int> и process<double> свои указатели. Что класть в __vtbl? Какой из них может переопределить потомок? Что будет, если потомок захочет третий вариант инстанциированного шаблона? Какого объема рантайм нужно притащить в исполняемую единицу, чтобы имея исходный код шаблона (и всего, от чего он зависит) на лету сформировать ещё один метод, не хуже тех двух скомпилированных и прооптимизированных в процессе сборки? Кто будет заниматься поиском адресов функций, которые могут быть вызваны из такого генерирующегося метода в зависимости от текущего Т? А если там тоже шаблонные функции?
                                  Ответить
                                  • а ну да. так и есть. В этом и .опа что вызвать темлейт из виртуала нельзя. Но я больше думал про ситуацию когда все специализации темплейта уже известны. Но вот если юзать как либу и надо вызвать новую специализацию. Тогда без JITа никуда
                                    Ответить
                                • Почини мне код, я не понимаю, что его не устраивает ;)
                                  http://rextester.com/ZFW58650
                                  Ответить
                                  • с# generic не template поэтому он не может кастить T в другой тип если не знает какие значения может получать T

                                    //Rextester.Program.Main is the entry point for your code. Don't change it.
                                    //Compiler version 4.0.30319.17929 for Microsoft (R) .NET Framework 4.5
                                    
                                    using System;
                                    using System.Collections.Generic;
                                    
                                    namespace Rextester
                                    {
                                        public class base1 { };
                                    
                                        public class class1 : base1 { };
                                    
                                        public class class2 : base1 { };
                                    
                                        public class Program
                                        {
                                            static int f<T>(T c) where T : base1 { return g(c); }
                                    
                                            static int g(base1 c)
                                            {
                                                switch (c)
                                                {
                                                    case class1 c1: return g(c1);
                                                    case class2 c2: return g(c2);
                                                }
                                    
                                                return 0;
                                            }
                                    
                                            static int g(class1 c) { return 1; }
                                            static int g(class2 c) { return 2; }
                                    
                                            public static void Main(string[] args)
                                            {
                                                //Your code goes here
                                                Console.WriteLine(f((class1)null));
                                                Console.WriteLine(f((class2)null));
                                            }
                                        }
                                    }


                                    вот ближайшая имплементация которая может работать
                                    Ответить
                                    • > с# generic не template
                                      ну наконец-то, спасибо!

                                      > он не может кастить T в другой тип
                                      Темплейт ничего не кастит.
                                      Он использует переданное точное значение типа вместо Т по всему своему коду для того, чтобы скомпилировать (не исполнить!) самостоятельный тип/функцию/метод, со всеми вытекающими (поэтому будет работать и вызов правильной перегруженной функции, и все остальное. А вот исходного шаблона никакого не останется.

                                      У меня всё.
                                      Ответить
                          • > это когда-нибудь может возникнуть в другой длл спустя 10 лет
                            А если 10 лет назад кто-то скомпилирует указанный ниже класс, у нас при вызове A().method(Class2017()) будут проблемы?
                            class A {
                                template <typename T> void method(T) {}
                            };

                            Если будут, то это по сути та же ситуация, но только она допустима в C++, а значит спокойно можно было за счёт знания исходного кода в vtable нагенерить вореций для всех случаев.
                            Ответить
                        • можно явно инстанцировать шаблонных наследников базового класса, переопределяющих его виртуальные методы, спору нет. И можно в виртуальную функцию передавать какой-нибудь typeid, чтобы эта виртуальная функция могла работать с информацией о типе. Но придется всего-навсего явно инстанцировать специализации шаблона, для каждого типа, ручками.

                          Если выносить информацию о типах в рантайм можно вообще много какой метушни натворить. Вот только делать каждый инт наследником Object'а и инстанцировать его в хипе - не самый производительный сценарий, вы не находите?

                          п.с. именно поэтому студия аж 15 гигов весит (если уже не больше)
                          Ответить
                  • кто нить расталкуйте нахера там стоит std::forward
                    Ответить
          • Засунь свой буст себе за щаку.
            Ответить

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