Преобразование void* в переменные разного типа

Господа, доброго времени суток!
Сломал себе уже весь мозг. Перечитал кучу статей, но, еще больше запутался.
Есть структура:


struct str_MenuItem { // Структура для хранения элемента меню и привязки его к переменным
  byte Item_ID; // ID-команды
  String Item_Name;  // Наименование пункта меню  
  void* _value; //Указатель на переменную, хранящую текущее значение команды
};

Есть массив с набором пунктов меню. Массив нужен, т.к. одни и те же пункты (но, не все) могут быть в разных меню (дабы не дублировать):

str_MenuItem menuItems[] = { // Массив со структурами элементов меню
  { 1, "Команда #1", &str_val },  // Ссылка на переменную типа String
  { 2, "Команда №2", &float_val1 }, // Ссылка на переменную типа float
  { 3, "Команда №3", &float_val2 }, // Ссылка на переменную типа float
  { 4, "Команда №4", &unt8t_val1 }, // Ссылка на переменную типа uint8_t
  { 5, "Команда №5", &unt16t_val1 }, // Ссылка на переменную типа uint16_t
};

Далее, при переборе пунктов меню, я хочу получить возможность: 1) вывести текущее значение на экран; 2) изменять значение в выбранной переменной.
Максимум, что я смог осилить, это получить значение из какого-то конкретного типа (например, float):

float* t = static_cast<float*>(menuItems[MenuID]._value); // Получаю ссылку на переменную
CurrentValue = String(*t); // Для вывода на экран или других целей...

А как узнать какой тип в текущей ссылке “menuItems[MenuID]._value”? В принципе, если отказаться от переменной типа String в первом пункте меню (можно извернуться), то все переменные можно свести к типу float и работать с ним, но, может есть какой способ? Тем более, что после манипуляций со значением, его придется вернуть переменной, ссылка которой хранится в menuItems[MenuID]._value. И не факт, что float нормально в uint16_t вернется. Или может можно вообще обойтись без преобразований, а менять значение сразу в “указателе”?

*menuItems[MenuID]._value = 50;

В общем, прошу помощи у гуру.

Добавить в структуру “команда” поле “тип переменной”: 1 - uint8, 2 - int8… (либо через enum).
Ну, а в обработчике команды, анализировать это поле и делать кастинг.

Вернее не в " команда", а в str_MenuItem.

1 лайк

Изначально было принято спорное решение, хранить указатель на void (что всё упрощает, но лишает Вас удобства типизации), а теперь Вам видите-ли не нравится, что нельзя работать с оригинальными типами. Вы уж что-нибудь выбирайте – или Вы работаете с типами или с void.

Если хотите продолжать работать с void, то делайте, что сказал @sadman41, он плохого не посоветует.

Хотите работать с оригинальными типами, забудьте про void и готовьтесь осваивать нормальное ООП. Могу помочь, но быстро это не получится – это не приём уровня “напиши тут букву зю и всё заработает”.

1 лайк

Через какой механизм это реализуется? Не то, что мне это сильно надо, но если не сильно замороченно - я б пробежался по соотв.разделу.

Кстати, в ESP32 Core пользовательские параметры данных через void* в таски и закидываются. Хошь число мечи, а хошь структуру. Это плохая практика?

Ух, я теперь и не знаю.
Про тип в структуре я тоже подумал, когда уже сообщение написал. Идея хорошая, но, тоже сильно усложняет код: нужно ветвления на каждом шагу плодить.

А вот как, отказавшись от void, работать с разными типами переменных?

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

Просто Евгений написал, что можно отойти от void* и решить вопрос “более элегантно” :slight_smile:

я почти уверен, что это не значит “более просто”. И если вы не знакомы с ООП, это точно не будет “более понятно”.
Но я бы тоже послушал.

2 лайка

Я 20 лет программистом работаю на заводе. ЧТо такое ООП я представление имею (в общих чертах :slight_smile: ). Правда там Delphi и MS SQL. А Ардуиной для себя для дома занимаюсь. Вот и проблемы от незнания С и С++.

это был приговор…

3 лайка

Мда, конструкция

float* t = static_cast<float*>(menuItems[MenuID]._value);
CurrentValue = String(*t);

себя не оправдала - CurrentValue всегда равна 0,00.
Или руки кривые, или…
В общем, надо идти другим путём.

нет никакого другого “или” в данном случае.

всё, что тебе ннада - dtostrf(…); читай

1 лайк

Ну, мужики, задача сформулирована слишком неконкретно и, гланое, противоречиво, поэтому трудно понять что именно нужно.

Например, вот эта фраза:

Но, если делать массив пунктов меню (массив таких структур) то они как раз будут дублироваться! Чтобы не дублировались, надо делать массив указателей на такие структуры.

Но с указателями есть другая задница. Вот ТС пишет:

Если с первым всё нормально, то ко второму есть три вопроса:

  1. если мы используем массив указателей и не дублируем структуры, то значение переменной будет меняться сразу во всех меню, в которые данная структура входит (т.к. она не дублируется и одна на все меню)! Это нормально? Если нормально, хорошо, а если нет, то никуда мы от дублирования не денемся;
  2. “изменять значение” – это как? Какие допустимы операции? Просто присвоить новое значение того же типа? Или набор возможных операция зависит от типа? Тогда неплохо бы иметь табличку “какие операции для каких типов допустимы”;
  3. наконец, есть более общий вопрос. Если значения не хранятся в структуре, а находятся снаружи и из структуры на них смотрит только указатель, зачем менять их именно через структуру? Вы же можете менять его там в программе сколько влезет, а когда меню потребуется показать текущее значение, оно спокойно возьмётся по указателю и покажется.

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

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

Два уровня наследования. На нижнем класс с виртуальными методами типа: const char * toString(void) const; и т.п., а на втором шаблон под все нужные типы, который эти методы реализует. Массив делать из структур нижнего типа типа, но методы будут вызываться из верхнего, на то они и виртуальные.

Но всего на свете я не знаю. Может какие ещё трюки существуют…

Именно потому, что пункты дублируются в разных меню, я и сделал массив пунктов меню.
Сами меню отличаются наборами пунктов и назначениями (одни для только просмотра, другие - для изменения параметров):

const byte menuINFO[] = { // Меню для просмотра
  1, 3, 4, 6, 7, 10  // ID пунктов меню из массива структур
};

const byte menuPRG[] = { // Меню для корректировки параметров
  2, 3, 5, 7, 9, 10, 11
};

Изначально, был план переменные отдельно, но, при перемещениях по меню с помощью switch возвращать нужную переменную. Но, когда пунктов меню (соответственно, и переменных) 127 штук, то такие ветвления не придают ни читабельности, ни быстродействия программе. Да и память в контроллере не безграничная. Поэтому, и было принято такое решение, чтобы не плодить дубликаты и программу не раздувать. Мне показалось это решение более логичным. Но, реализовать мозгов не хватило.

Есть параметр в пункте меню (пусть будет какой-то коэффициент, или заданная температура). Значение этого параметра нужно просто показать в одном меню, а в другом меню не просто показать, но, и позволить пользователю изменить значение параметра (value++, value-- и т.д.), сохранить его для передачи далее (сохранить в EEPROM, передать по сети…).

см. п.2 про switch

Я сердечно и от всей души поздравляю всех с праздником!!! За пример буду безмерно признателен!

Почему ID, а не указатели? Если бы были указатели, то не нужен был бы массив структур! А, значит, структуры имели бы прав быть разного размера! Да и доступ по указателю чуть быстрее, чем по ID. Точно нужен массив ID? Я бы так делать не стал.

Трижды перечитал, так инего и не понял. Вы вопрос поняли? Допустим некая структура с каким-то там ID входит в два разных меню. Но если Вы поменяете значение для одного меню, то поменяется и для другого, т.к. структура одна. Это так и должно быть?

Нет, вот пожалуйста, не надо никаких “и т.д.” Вы потому и написать не можете, что вместо продуманного проекта у Вас “и т.д.”.

Вы выше писали, что возможно:

Ну и что должно означать Ваше “value++, value-- и т.д.” для переменной типа String?

Здесь нужна максимальная ясность и однозначность, иначе что бы Вы ни сделали, выяснится, что это не то и “ботинки жмут”.

Я ничего не понял про switch, начиная с того, “о каком свиче речь” и заканчивая тем, а зачем там switch вообще нужен.

ID нужны, чтобы найти нужный пункт меню. ID прилетают с другого контроллера, а также отправляются ему. Это система команд. Каждая команда имеет свой ID.

Да, на это и был расчет! Одна переменная с одним и тем же значением. Просто используются в разных меню.

Можно изменить значение на 1 для uint8_t и uint16_t, и на 0,1 для float, 1 и 0 для bool.

Переменная типа String нужна только для чтения. По сути - это константа. Сообщает пользователю, что команда не найдена. Сделано на случай, если будет попытка вызвать неправильную команду. Функция поиска пункта меню по ID вернет 0 (ноль). Это пункт меню в массиве пунктов {0, “Команда не поддерживается”, nullptr}.

Изначально было так (нужно же было как-то выбрать значение нужной переменной:

  switch (MenuLevel) { // Меню в режиме "только чтение"
    case InfoMode:
      Serial.print("InfoMode: ");
      switch (MenuPos - 1) {
        case menuINFO_comm1:
          CurrentValue = String(float_var1, 1);
          break;
        case menuINFO_comm2:
          CurrentValue = String(uint8_var1, 0);
          break;
        case menuINFO_comm3:
          CurrentValue = String(uint8_var2, 0);
          break;
        case menuINFO_comm4:
          CurrentValue = String(float_var2, 0);
          break;
      }
      break;
    case ProgramMode: // Меню изменения настроек
      switch (MenuPos - 1) {
      switch (MenuPos - 1) {
        case menuINFO_comm1:
          CurrentValue = String(float_var1, 1);
          break;
        case menuINFO_comm2:
          CurrentValue = String(uint8_var1, 0);
          break;
        case menuINFO_comm3:
          CurrentValue = String(uint8_var2, 0);
          break;
        case menuINFO_comm4:
          CurrentValue = String(float_var2, 0);
          break;
      }
      break;
  }

Простите уж за тупость - не могу нормально выразить, что задумывалось :frowning:

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

На том что вы описали, у меня пока только складывается представление, что все данные должны быть в едином формате (скорее всего String) который вы уже будете приводить к нужному типу данных. Но тут возникает проблема, с определением этого самого типа. Я так понимаю меню динамическое и за ранее какой будет тип вы не знаете. Соответственно нужен идентификатор типа данных к которому приводить.

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

нда, зашел так зашел…

3 лайка