Как получить тип данных переменной?

Я использую Arduino и хотел бы знать, существует ли функция, которая возвращает тип данных переменной. То есть я хотел бы запустить что-то вроде следующего:

// Note: 'typeof' is a sample function that should return the data type.
Serial.println(typeof(myVar));

, 👍14

Обсуждение

Почему вам нужно это делать? тип переменной должен быть известен с момента ее объявления, @sachleen

@sachleen Неправда. Справочная информация по Arduino печально известна тем, что не говорит вам о типах возвращаемых функций. Я должен определить как auto, а затем использовать кучу перегрузок функций, чтобы узнать. RTTI было бы проще узнать., @brewmanz


5 ответов


Лучший ответ:

20

В типичной программе на C++ вы бы использовали оператор typeid, например:

std::cout << typeid(myVar).name();

Однако для этого требуется функция компилятора, называемая Информацией о типе среды выполнения (RTTI). Он отключен в среде IDE Arduino, предположительно, потому, что он увеличивает требования к памяти во время выполнения программы.

Вы можете получить более подробную информацию о стоимости ресурсов здесь: https://stackoverflow.com/questions/579887/how-expensive-is-rtti

Однако любой полнофункциональный компилятор C++ определенно будет поддерживать RTTI. Если вы хотите попробовать использовать стороннюю IDE (например, Eclipse с плагином Arduino), вы можете легко включить ее. Хотя, вероятно, это не стоит хлопот только из-за этого.


Альтернатива
Более эффективным (но менее гибким) решением было бы использование подхода класса признаков. Это включает в себя какое-то фанковое метапрограммирование шаблонов:

// Generic catch-all implementation.
template <typename T_ty> struct TypeInfo { static const char * name; };
template <typename T_ty> const char * TypeInfo<T_ty>::name = "unknown";

// Handy macro to make querying stuff easier.
#define TYPE_NAME(var) TypeInfo< typeof(var) >::name

// Handy macro to make defining stuff easier.
#define MAKE_TYPE_INFO(type)  template <> const char * TypeInfo<type>::name = #type;

// Type-specific implementations.
MAKE_TYPE_INFO( int )
MAKE_TYPE_INFO( float )
MAKE_TYPE_INFO( short )

Вы можете добавить строки MAKE_TYPE_INFO(..) для любого типа, который вы хотите, включая имена пользовательских классов. Затем вы могли бы использовать его следующим образом:

int myVar = 17;
Serial.println( TYPE_NAME(myVar) );

Все, что вы не определили с помощью MAKE_TYPE_INFO (..), будет отображаться как "unknown".

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

ПРАВКА: Стоит отметить, что оператор typeof не является стандартным C++, но поддерживается несколькими компиляторами, такими как GCC. По сути, это более старый эквивалент decltype, который фигурирует в стандарте C++11.

,

очень информативный и обстоятельный ответ! был бы признателен за больше такого рода, @Omer


20

Я использую простой глупый подход...

// serial print variable type
void types(String a) { Serial.println("it's a String"); }
void types(int a) { Serial.println("it's an int"); }
void types(char *a) { Serial.println("it's a char*"); }
void types(float a) { Serial.println("it's a float"); }
void types(bool a) { Serial.println("it's a bool"); }

Это концепция полиморфизма, при которой создается несколько функций с разными типами параметров, но с одним и тем же именем функции. Во время выполнения будет вызвана функция, соответствующая нужному количеству аргументов и типу(типам) аргументов. Надеюсь, это объяснение поможет.

,

Не могли бы вы немного расширить свой ответ*, кратко объяснив, как это работает?, @Greenonline

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

Простой, понятный без знания шаблонов. Может быть расширен для возврата перечисления типа., @voidPointer


3

Основываясь на ответе @snakeNET (который я расцениваю как перегрузку функций, а не полиморфизм)...

Более общий способ-передать указатель на печать, позволяя использовать любой класс, связанный с печатью (не только последовательный); также передавать объекты по ссылке, например

void types(Print* p, const String&) { p->print("it's a String"); } // for each type
...
Serial.print("Type is "); types(&Serial, myStr); Serial.println();

Обратите внимание, что я изменил "println" на "print"; снова, сделав его более общим.

,

Я думаю, что значение термина **полиморфизм** несколько изменилось за десятилетия, чтобы теперь включать перегрузку функций *вне* классов ООП, а также перегрузку функций-членов *внутри* классов. Когда я начал изучать программирование, это касалось только переопределения методов виртуального класса в классах-потомках., @tim


1

Я протестировал форму шаблона, форму с длинными руками и макрос ___ _ _ _ _ _ _ _ _ и получил разные результаты. Различия приведены в последних двух строках таблицы.

Форма шаблона 1

Авторство: Питер Блумфилд

MAKE_TYPE_INFO( bool )
MAKE_TYPE_INFO( bool* )
MAKE_TYPE_INFO( char )
MAKE_TYPE_INFO( char* )
MAKE_TYPE_INFO( double )
MAKE_TYPE_INFO( double* )
MAKE_TYPE_INFO( float )
MAKE_TYPE_INFO( float* )
MAKE_TYPE_INFO( int )
MAKE_TYPE_INFO( int* )
MAKE_TYPE_INFO( short )
MAKE_TYPE_INFO( short* )
MAKE_TYPE_INFO( String )
MAKE_TYPE_INFO( String* )
MAKE_TYPE_INFO( char[5] )
MAKE_TYPE_INFO( char(*)[5] )

Форма Длинной Руки

Атрибуция:

  1. Змеиная сеть
  2. брюманц
  3. Изменено мной, чтобы быть более общим.
const char* TypeOf(const bool&)   { static const char type[] = "bool";    return type; }
const char* TypeOf(const bool*)   { static const char type[] = "bool*";   return type; }
const char* TypeOf(const char&)   { static const char type[] = "char";    return type; }
const char* TypeOf(const char*)   { static const char type[] = "char*";   return type; }
const char* TypeOf(const double&) { static const char type[] = "double";  return type; }
const char* TypeOf(const double*) { static const char type[] = "double*"; return type; }
const char* TypeOf(const float&)  { static const char type[] = "float";   return type; }
const char* TypeOf(const float*)  { static const char type[] = "float*";  return type; }
const char* TypeOf(const int&)    { static const char type[] = "int";     return type; }
const char* TypeOf(const int*)    { static const char type[] = "int*";    return type; }
const char* TypeOf(const String&) { static const char type[] = "String";  return type; }
const char* TypeOf(const String*) { static const char type[] = "String*"; return type; }
//const char* const TypeOf(const char[5])    { static const char* type = "char[5]";    return type; }  // Decays to char* so generates compiler redefinition warning.
const char* const TypeOf(const char(*)[5]) { static const char* type = "char(*)[5]"; return type; }

Форма шаблона 2: __КРАСИВАЯ_ФУНКЦИЯ__ Макрос

Атрибуция:

  1. Оригинальная string версия Владимира Талыбина
  2. Я изменил версию массива символов.

Использование строки

template <class T>
String type_name(const T&)
{
    String s = __PRETTY_FUNCTION__;
    int start = s.indexOf("[with T = ") + 10;
    int stop = s.lastIndexOf(']');
    return s.substring(start, stop);
}

Использование массива символов

Этот вариант имеет более низкие требования к памяти.

template <typename T>
char* type_name(const T&)
{
    char* pf = __PRETTY_FUNCTION__;
    char* begin = strstr(pf, "[with T = ") + 10;
    char* end = strrchr(pf, ']');
    *end = 0;
    return begin;
}

Тестовый код

bool b = false;
char c = 'a';
double d = 1.2;
float f = 3.4;
int i = 5;
String s = "6789";
char chars[] = "1234";

Serial.println(F("TypeOf() Test"));
Serial.println(TypeOf(b));
Serial.println(TypeOf(&b));
Serial.println(TypeOf(c));
Serial.println(TypeOf(&c));
Serial.println(TypeOf(d));
Serial.println(TypeOf(&d));
Serial.println(TypeOf(f));
Serial.println(TypeOf(&f));
Serial.println(TypeOf(i));
Serial.println(TypeOf(&i));
Serial.println(TypeOf(s));
Serial.println(TypeOf(&s));
Serial.println(TypeOf(chars));
Serial.println(TypeOf(&chars));

Serial.println(F("TYPE_NAME() Test"));
Serial.println(TYPE_NAME(b));
Serial.println(TYPE_NAME(&b));
Serial.println(TYPE_NAME(c));
Serial.println(TYPE_NAME(&c));
Serial.println(TYPE_NAME(d));
Serial.println(TYPE_NAME(&d));
Serial.println(TYPE_NAME(f));
Serial.println(TYPE_NAME(&f));
Serial.println(TYPE_NAME(i));
Serial.println(TYPE_NAME(&i));
Serial.println(TYPE_NAME(s));
Serial.println(TYPE_NAME(&s));
Serial.println(TYPE_NAME(chars));
Serial.println(TYPE_NAME(&chars));

Serial.println(F("type_name() Test"));
Serial.println(type_name(b));
Serial.println(type_name(&b));
Serial.println(type_name(c));
Serial.println(type_name(&c));
Serial.println(type_name(d));
Serial.println(type_name(&d));
Serial.println(type_name(f));
Serial.println(type_name(&f));
Serial.println(type_name(i));
Serial.println(type_name(&i));
Serial.println(type_name(s));
Serial.println(type_name(&s));
Serial.println(type_name(chars));
Serial.println(type_name(&chars));

Результаты

TypeOf() ИМЯ ТИПА() имя типа()
бул бул бул
бул* бул* бул*
шар шар шар
чар* шар* шар*
двойной двойной двойной
двойной* двойной* двойной*
float float float
float* float* float*
инт инт инт
инт* int* int*
Строка Строка Строка
Строка* Строка* Строка*
шар* чар[5] чар [5]
char(*)[5] символ(*)[5] символ (*)[5]

Некоторые Выводы

  1. У функции TypeOf() есть проблема с затухающим указателем.
  2. TypeOf() и TYPE_NAME() должны иметь отдельные определения с длинной формой или MAKE_TYPE_INFO.
  3. type_name() использует макрос __PRETTY_FUNCTION__ для автоматического получения имени типа в функции шаблона, поэтому нет необходимости индивидуально определять различные типы.
  4. Шаблон формы 1 имеет самые низкие требования к памяти.

Вопросы

  1. Поскольку форма шаблона 2 имеет более высокие требования к памяти, чем форма шаблона 1, как ее можно преобразовать из Формы 2 в Форму 1?
  2. Как статический символ const * будет инициализирован из подстроки массива символов, полученного из макроса _____ _ _ _ _ _ _ и передан в качестве аргумента шаблона во время компиляции?

Что-то вроде этого псевдокода?

static const char * name = substr(__PRETTY_FUNCTION__, begin, end);

Возможно, это постоянное выражение, которое может быть присвоено члену структуры шаблона во время компиляции? (Я использовал цикл for, потому что strstr и strrchr не являются constexpr.)

template <typename T>
constexpr char* type_name(const T&)
{
    char* pf = __PRETTY_FUNCTION__;
    char* begin;
    char* end;
    begin = end = pf;
    bool found_open_square_bracket = false;
    for (; *pf != 0; pf++)
    {
      if (!found_open_square_bracket && *pf == '[')
      {
        begin = pf + 10;
        found_open_square_bracket = true;
      }
      if (*pf == ']')
      {
        end = pf;
      }
    }
    *end = 0;
    return begin;
}
,

Обратите внимание, что: 1. Когда вы передаете массив в качестве аргумента функции, он распадается на указатель на его первый элемент. 2. char*[5] - это массив указателей, тогда как &chars - указатель на массив., @Edgar Bonet

@Juraj, Извини. Я думал, что первый абзац охватывает это в контексте этой темы. Я объясню это яснее., @tim

Это скорее ограничение версии C÷+ в Arduino. На ПК я бы вернул " std::string_view в этом случае. На Arduino вы можете вернуть структуру "const char* и "size_t". Это решит вашу проблему с памятью., @Vladimir Talybin

Я бы также разделил часть шаблона и часть синтаксического анализа на 2 функции, чтобы компилятор генерировал меньше кода для каждого запрошенного типа., @Vladimir Talybin


1

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

template <class T>
String type_name(const T&)
{   
    String s = __PRETTY_FUNCTION__;

    int start = s.indexOf("[with T = ") + 10;
    int stop = s.lastIndexOf(']');

    return s.substring(start, stop);
}

Используйте его вот так

double pi = 3.14;
const char* str = "test";
    
Serial.println(type_name(pi));
Serial.println(type_name(str));

ИЗМЕНИТЬ:

Некоторые улучшения основаны на анализе Тима.

Сначала у меня есть комментарий к варианту, который имеет более низкие требования к памяти. __PRETTY_ФУНКЦИЯ__ - это литеральная строка (константа), которую не следует изменять. Поэтому моя версия const, которая не использует кучу, следующая.

template <size_t N>
const char* extract_type(const char (&signature)[N])
{
    const char* beg = signature;
    while (*beg++ != '=');
    ++beg;

    const char* end = signature + N;
    for (; *end != ']'; --end);

    static char buf[N];
    char* it = buf;

    for (; beg != end; ++beg, ++it)
        *it = *beg;
    *it = 0;

    return buf;
}

template <class T>
const char* type_name(const T&)
{
    return extract_type(__PRETTY_FUNCTION__);
}

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

template <size_t N>
const char* extract_type(const char (&signature)[N])
{
    const char* beg = signature + 42;
    const char* end = signature + N - 2;

    static char buf[N - 43];
    char* it = buf;

    for (; beg != end; ++beg, ++it)
        *it = *beg;
    *it = 0;

    return buf;
}

template <class T>
const char* type_name(const T&)
{
    return extract_type(__PRETTY_FUNCTION__);
}

Хотя вышесказанное, конечно, нормально, его можно улучшить и дальше. Вот моя версия функции constexpr, упомянутая @tim. Он использует постоянное смещение, как указано выше, чтобы сократить его. Этот код генерирует только извлеченную строку типа, доказательство здесь.

#include <utility>

template <class T, std::size_t... I>
const char* type_name(std::index_sequence<I...>)
{
    static constexpr char name[] = { __PRETTY_FUNCTION__[I + 60]..., 0 };
    return name;
}

template <class T>
const char* type_name(const T&)
{
    return type_name<T>(
        std::make_index_sequence<sizeof(__PRETTY_FUNCTION__) - 44>());
}

Минимальное требование для этого-C++14 из-за особенностей последовательности. Они могут быть повторно реализованы (см. Определение здесь), чтобы адаптировать его к C++11.

,

Это здорово, но " int stop = s.lastIndexOf (]'); "лучше, потому что в некоторых типах есть" []., @tim

@тим Тру, спасибо тебе! Изменит это., @Vladimir Talybin