На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
  
> специализация шаблона функции для константной строки - литерала
    Есть класс, у него есть функция "отправить что угодно заданного размера". Есть шаблонная функция, которой можно передать что угодно, функция сама подставит размер:
    ExpandedWrap disabled
      class a
      {
      public:
         void send(void const * from, size_t size);
       
         template <typename T>
         void send(T const & data)   { send(&data, sizeof(T); }
      }


    По условиям протокола передачи строки передаются без завершающего нуля. Описанная выше реализация при вызове send("слава мне, победителю драконов!") отправляет строку с завершающим нулем. приходится писать что-то вроде
    ExpandedWrap disabled
      {
        char const String[] = "слава мне, победителю драконов!";
        A.send(String, sizeof(String) - 1);
      }

    Конечно, это можно обернуть в макрос, но душа желает красоты.

    Я хочу переопределить функцию send(), чтобы она сама вычитала единицу из размера, если ей передали константный массив char.
    Ни один из следующих вариантов не срабатывает, вместо них раскручивается указанная выше реализация шаблона:
    ExpandedWrap disabled
          template<size_t length>
          void response(char const string[length])    { response(string, length - 1); }
       
          template<size_t length, char const T[length]>
          void response(char const string[length])    { response(string, length - 1); }


    Я хочу чего-то невозможного?
      ExpandedWrap disabled
        void send(const char *data) { send(data, strlen(data)); }
         
        template <size_t length>
        void send(const char (&data)[length]) { send(&data, length-1); }
      Сообщение отредактировано: Qraizer -
        Спасибо. Да уж, сильно меня переклинило. Только send(data, length-1); должно быть, без "&".
          Цитата Dushevny @
          Да уж, сильно меня переклинило
          Ну я бы не назвал это очевидным решением. Массивы, строковые литералы не исключение, сводятся к указателям, поэтому указание размера массива в параметре функции по сути является комментарием и потому игнорируется. Но сведение не осуществляется для ссылочных типов, поэтому компилятор сможет вывести размер массива, если он известен в точке вызова.
          Цитата Dushevny @
          Только send(data, length-1); должно быть, без "&".
          Почему? Основная же функция send(void const*, size_t) или я что-то не понял?
            Цитата Qraizer @
            Ну я бы не назвал это очевидным решением.
            Да знал я про такой синтаксис при объявлении специализации для массива, но... забыл.
            Цитата Qraizer @
            Почему? Основная же функция send(void const*, size_t) или я что-то не понял?
            Потому что массив неявно приводится к типу "указатель на нулевой элемент". &data тоже компилится, но лишняя писанина.
            Сообщение отредактировано: Dushevny -
              Цитата Dushevny @
              Я хочу переопределить функцию send(), чтобы она сама вычитала единицу из размера, если ей передали константный массив char.

              Попробуй так:
              ExpandedWrap disabled
                class a
                {
                 public:
                //  void send(const void* from, size_t size);
                  void send(const char* from, size_t size=0);
                  template <typename T> void send(T const & data) { send((const char*)&data, sizeof(T)); }
                };
                void a::send(const char* from, size_t size)
                {
                 if(size==0) size=strlen(from);
                //...
                }


              Тогда:
              ExpandedWrap disabled
                  char const* pString = "слава мне, победителю драконов!";
                  A.send(pString);
                  A.send("ура, ура, ура !");
              Сообщение отредактировано: ЫукпШ -
                Цитата ЫукпШ @
                Попробуй так:
                Не годится. Если я захочу передать структуру - ваш код применит к ней strlen(). К тому же проверка лишнего условия на этапе выполнения. Можно переопределить функцию для char const * без шаблона, но остается лишний вызов strlen() для строки, размер которой известен компилятору. Конечно, если эту функцию сделать встраиваемой, то современные оптимизаторы чаще всего заменят вызов strlen() на известный им размер, но Qraizer уже подсказал правильное решение. Именно то, которое было нужно.
                  Цитата Dushevny @
                  Цитата ЫукпШ @
                  Попробуй так:
                  Не годится. Если я захочу передать структуру - ваш код применит к ней strlen().

                  Нет.
                  strlen будет применён только для типа const char*.
                  Для других типов будет использована шаблонная функция.
                  А для них sizeof(T) не равен 0.
                  Сообщение отредактировано: ЫукпШ -
                    Цитата ЫукпШ @
                    strlen будет применён только для типа const char*.
                    Ага, понял. Невнимательно прочитал ваш код. Все равно некрасиво: основная функция для передачи чего угодно принимает указатель на char const *, хотя в языке для таких случаев существует специальный тип "указатель на что угодно", т.е. void (const) *. И в шаблоне приходится явно приводить указатель на переданный тип к char const *, и в почти любой другой точке вызова основной функции придется засорять код явным приведением к char const *, в то время как к void const * приведение осуществляется неявно. В общем, работать оно будет, но рушится строгая типизация. Неаккуратненько, а потому некрасиво.
                    Сообщение отредактировано: Dushevny -
                      Цитата Dushevny @
                      В общем, работать оно будет, но рушится строгая типизация. Неаккуратненько, а потому некрасиво.

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


                      ExpandedWrap disabled
                        void send(const void *data, size_t sz);
                        void send(const std::string_view &sv) {send(sv.data(), sv.size());}
                         
                        template <typename T, typename = std::enable_if_t<!std::is_convertible_v<std::string_view>>>
                        void send(const T &val) {send(&val, sizeof(val));}
                        Цитата Dushevny @
                        Qraizer уже подсказал правильное решение. Именно то, которое было нужно.
                        Надеюсь, ты понимаешь, что этот код легко ломается обычным массивом, типа char msg[256] = { "Hello, world!" };
                          Цитата Qraizer @
                          Цитата Dushevny @
                          Qraizer уже подсказал правильное решение. Именно то, которое было нужно.
                          Надеюсь, ты понимаешь, что этот код легко ломается обычным массивом, типа char msg[256] = { "Hello, world!" };

                          А почему ?
                            Потому что будет передан весь массив на 256 символов вместо только строки без NUL в конце.
                              Цитата Qraizer @
                              Потому что будет передан весь массив на 256 символов вместо только строки без NUL в конце.

                              Qraizer,
                              "char msg[256]", передаваемый как "msg" будет воспринят компилятором как "char* msg".
                              Из предложенных прототипов функций немедленно будет выбрана функция
                              "send(const char*)", поскольку преобразование типа char* -> const char*
                              полностю допустимо и компилятор сделает его обязательно.
                              ---
                              Вот эта конструкция:
                              ExpandedWrap disabled
                                char msg[256]="123";  
                                // или
                                char msg[256]={"345"};

                              приведёт к тому, что будет выделен массив указанных размеров,
                              в который сначала будет помещена указанная строка, а весь остаток будет занулён.
                              Таким образом, вышеупомянутый вариант это, как бы, строка с длинным нулевым "хвостом".
                              А попытка сделать :
                              ExpandedWrap disabled
                                char msg[3]="123";

                              приведёт к синтаксической ошибке.
                              Сообщение отредактировано: ЫукпШ -
                                Цитата ЫукпШ @
                                char msg[256], передаваемый как "msg" для компилятора будет char* msg.
                                Из предложенных прототипов функций немедленно будет выбрана функция
                                "send(const char* ...", поскольку преобразование типа char* -> const char*
                                полностю допустимо и компилятор сделает его обязательно.

                                С чего вдруг?
                                У него вызовется вот эта функция, при передаче массива.
                                Цитата Qraizer @
                                template <size_t length>
                                void send(const char (&data)[length]) { send(&data, length-1); }


                                Так как она как раз массив принимает, а char msg[256] - это в первую очередь массив.
                                  Цитата Wound @
                                  С чего вдруг?
                                  У него вызовется вот эта функция, при передаче массива.
                                  Цитата Qraizer @
                                  template <size_t length>
                                  void send(const char (&data)[length]) { send(&data, length-1); }

                                  Вот эта функция будет вызвана:
                                  ExpandedWrap disabled
                                    void send(const char *data) { send(data, strlen(data)); }
                                    Цитата ЫукпШ @
                                    Вот эта функция будет вызвана:

                                    С чего вдруг? Ты судя по всему не представляешь как работают шаблоны в С++
                                    Вот например: https://ideone.com/WDe9qp
                                    ExpandedWrap disabled
                                          #include <iostream>
                                          
                                          
                                          void send(const char *data)
                                          {
                                              std::cout << "void send(const char *data) " << std::endl;
                                          }
                                          
                                          template <size_t length>
                                          void send(const char (&data)[length])
                                          {
                                              std::cout << "void send(const char (&data)[length]) " << std::endl;
                                          }
                                          
                                          int main()
                                          {
                                              char msg[256] = { "Hello, world!" };
                                              char msg2[256] = "Hello, world!";
                                              send(msg);
                                              send(msg2);
                                              return 0;
                                          }


                                    Добавлено
                                    Просто я не понимаю, с чего вдруг компилятор должен делать неявный каст массива к указателю и вызывать эту функцию -> void send(const char *data), если есть сигнатура функции void send(const char [256] data), которая подходит безо всяких неявных кастов?
                                    Сообщение отредактировано: Wound -
                                      Цитата Wound @
                                      Просто я не понимаю, с чего вдруг компилятор должен делать неявный каст массива к указателю и вызывать эту функцию ->

                                      Да всё просто - я написал этот тест целиком и посмотрел как он работает.
                                      А что это - вообще не понятно:
                                      ExpandedWrap disabled
                                        void send(const char (&data)[length]) { send(&data, length-1); }

                                      Если мы хотим передавать массив (наинужнейшая функция), то зачем "length-1" ?
                                      Если мы хотим передавать строку (или строку в массиве), то для этого имеется уже подходящая
                                      функция. Её и использует компилятор.
                                      ---
                                      В некотором роде нагло и цинично можно заявить, что массив в "С" - это форма записи
                                      указателя. Который сразу проинициализирован на область памяти указанного размера.
                                      ---
                                      Тема в целом очень печальная.
                                      Очень не удачный вариант - совмещать формирование пакета с передачей.
                                      1. Что будет, если автора попросят сделать сборку x64 ?
                                      Скрытый текст

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

                                      2. Как различить структуры на стороне приёма ? Или, например, массив от строки ?
                                      Строки символов разных форматов (CP1251, UTF8, UTF16) ?
                                      ExpandedWrap disabled
                                        struct
                                        {
                                         char a;
                                         int b;
                                        };
                                        struct
                                        {
                                         int a;
                                         char b;
                                        }

                                      3. как вообще обнаружить окончание передачи ?
                                      4. А если попросят шифровать траффик ?
                                      5. А если поменять ключи компиляции (по выравниванию) ?
                                      итд итп...
                                      Сообщение отредактировано: ЫукпШ -
                                        Цитата ЫукпШ @
                                        А что это - вообще не понятно:
                                        Прочитайте первое сообщение темы.
                                          Цитата ЫукпШ @
                                          В некотором роде нагло и цинично можно заявить, что массив в "С" - это форма записи указателя.
                                          Садись, два. Сожги учебник, в котором так написано.
                                            Цитата ЫукпШ @
                                            Что будет, если автора попросят сделать сборку x64 ?
                                            Это маленькое устройство размером со спичечный коробок. 64 бита там излишни. А вот плюсы к Сям очень кстати.

                                            Даже стало интересно: а что сломается в этом коде, если его собрать под 64-битную целевую платформу? Я так сразу подводных камней не вижу. Уверен, что на 8- и 16-битных микроконтроллерах этот код будет работать так же, как сейчас работает на 32-битном.

                                            Цитата ЫукпШ @
                                            Как различить структуры на стороне приёма ? Или, например, массив от строки ?
                                            По посланному коду запроса.
                                            Цитата ЫукпШ @
                                            как вообще обнаружить окончание передачи ?
                                            Этим занимаются более нижние уровни протокола. Скажу по секрету - в начале пакета передается его длина. Бывают и другие варианты, byte-stuffing, например (пример можно найти в описании HDLC).
                                            Цитата ЫукпШ @
                                            А если попросят шифровать траффик ?
                                            Этим займутся более нижние уровни протокола, функции которых вызываются из send()
                                            Цитата ЫукпШ @
                                            А если поменять ключи компиляции (по выравниванию) ?
                                            У меня встречный вопрос как и про 64-битную целевую платформу: а что должно поломаться? указатель на void в параметрах глобальной функции передачи подразумевает невыровненные данные и именно с невыровненными данными идет работа. Так что от смены ключей компиляции кроме размера программы и пренебрежительно малого изменения времени реакции ничего не изменится.
                                            Сообщение отредактировано: Dushevny -
                                              ЫукпШ имеет в виду, что отсутствие типизации может привести к поломке протоколов обмена, когда одна из сторон делает это без согласования с другой. Например, передавая 64-битный int, когда приёсмная сторона ожидает 32-битный пакет, может быть неверно ею истолкован. Но это не проблема уровня сырого обмена данными, это должно решаться интерфейсами взаимодействия. Какой-нибудь std::basic_ostream<>::write() тоже не знает, что он пишет, и не может отвечать за то, как, верно или неверно, его посылку, считанную std::basic_istream<>::read(), будут потом парсить. Если тут и будут проблемы, то они явно относятся к уровню выше сырого обмена массивами байт.
                                                Цитата Qraizer @
                                                Например, передавая 64-битный int, когда приёсмная сторона ожидает 32-битный пакет,
                                                Понятно. Для этого 20 лет назад в Стандарт Сей введен stdint.h, в котором объявлены (u)intXX_t и подобные. В мире микроконтроллеров "чистыми" int, short, long, long long пользуются только начинающие или те, кто учится на своих ошибках. Из фундаментальных типов используется только char и только для хранения символов, все остальное - только типы из stdint.h. Поэтому одни и те же куски кода я, например, использую на 8- 16- 32-битных контроллерах и на компе под 32- и 64-битными линухами и виндовсами.
                                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                                0 пользователей:


                                                Рейтинг@Mail.ru
                                                [ Script execution time: 0,0625 ]   [ 17 queries used ]   [ Generated: 19.03.24, 06:26 GMT ]