user1015: I am using Arduino and I would like to know if there is a function that returns the data type of a variable. That is, I would like to run something as like the following:
// Note: 'typeof' is a sample function that should return the data type.
Serial.println(typeof(myVar));


user68310: I tested the template form, the long-hand form, and the __PRETTY_FUNCTION__ macro and got different results. See the last two rows of the table for the differences.
Template Form 1
Attribution: Peter Bloomfield
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] )

Long-Hand Form
Attribution:

SnakeNET
brewmanz
Tweaked by me to be more generic.

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; }

Template Form 2: __PRETTY_FUNCTION__ Macro
Attribution:

Original String version by Vladimir Talybin
Tweaked char array version by me.

Using String
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);
}

Using char array
This variation has lower memory requirements.
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;
}

Test Code
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));

Results




TypeOf()
TYPE_NAME()
type_name()




bool
bool
bool


bool*
bool*
bool*


char
char
char


char*
char*
char*


double
double
double


double*
double*
double*


float
float
float


float*
float*
float*


int
int
int


int*
int*
int*


String
String
String


String*
String*
String*


char*
char[5]
char [5]


char(*)[5]
char(*)[5]
char (*)[5]



Some Conclusions

TypeOf() has an issue with a decaying pointer.
TypeOf() and TYPE_NAME() have to have individual definitions with long-hand form or MAKE_TYPE_INFO.
type_name() uses the __PRETTY_FUNCTION__ macro to automatically get the type name within the template function, therefore there is no need to individually define the different types.
Template Form 1 has the lowest memory requirements.

Questions

Since Template Form 2 has higher memory requirements than Template Form 1, how could it be converted from Form 2 to Form 1?
How would a static const char * be initialised from a substring of a char array obtained from the __PRETTY_FUNCTION__ macro and passed as a template argument at compile-time?

Something like this pseudo code?
static const char * name = substr(__PRETTY_FUNCTION__, begin, end);

Perhaps this constant expression which can be assigned to a template struct member at compile-time? (I used a for loop because strstr and strrchr are not 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;
}


user78500: There also a trick using compiler predefined macro. The advantage is it can print any type.
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);
}

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

EDIT:
Some improvements based on tim's analyze.
First I have a comment on variation that has lower memory requirements. __PRETTY_FUNCTION__ is a literal string (a constant) that should not be changed. So my const version that do not use heap is following.
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__);
}

A less safe but more efficient version using the fact that offset to type we want extract is a constant (always the same). If function signature changes (like put it in a namespace) the offset will change too. But come on, set it once, write a comment and it will work forever :).
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__);
}

While above is ok ofcourse it can be improved further. Here is a my version of constexpr function mentioned by @tim. It uses constant offset as above to keep it short. This code generates only extracted string of type, the proof is here.
#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>());
}

The minimum requirement for this is C++14 because of sequence traits. These can be re-implemented (see definition here) to adapt it to C++11.

user54502: Building on @snakeNET's answer (which I regard as function overload rather than polymorphism)...
A more generic way would be to pass in a pointer to Print, allowing any Print related class to be used (not just Serial); also pass objects by reference e.g.
void types(Print* p, const String&) { p->print("it's a String"); } // for each type
...
Serial.print("Type is "); types(&Serial, myStr); Serial.println();

Note that I has changed 'println' to 'print'; again, making it more generic.

user34212: I use a simple stupid approach...
// 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"); }

This is the concept of polymorphism where multiple functions with different parameter types are created but with the same function name. During run time, the function matching the right number of arguments and argument type(s) will get called. Hope this explanation helps.

user42: In a typical C++ program, you would use the typeid operator, like this:
std::cout << typeid(myVar).name();

However, that requires a compiler feature called Runtime Type Information (RTTI). It's disabled in the Arduino IDE, presumably because it tends to increase the runtime memory requirements of the program.
You can get more information about the resource cost here: https://stackoverflow.com/questions/579887/how-expensive-is-rtti
Any fully-featured C++ compiler will definitely support RTTI though. If you want to try using a 3rd party IDE (such as Eclipse with the Arduino plugin) then you could easily enable it. It's probably not worth the hassle just for that though.

Alternative
A higher-performing (but less flexible) solution would be to use a traits class approach. This involves some funky template meta-programming:
// 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 )

You can add MAKE_TYPE_INFO(..) lines for any type you want, including the names of custom classes. You could then use it like this:
int myVar = 17;
Serial.println( TYPE_NAME(myVar) );

Anything you don't define using MAKE_TYPE_INFO(..) will show up as "unknown".
That's some fairly advanced stuff in there, so I won't try to explain how it all works here. There are various tutorials on the web though about C++ template programming if you're interested.
EDIT: It's worth noting that the typeof operator isn't standard C++, but is supported by a few compilers, such as GCC. It's basically an older equivalent of decltype, which appears in the C++11 standard.

1015: <p>I am using Arduino and I would like to know if there is a function that returns the data type of a variable. That is, I would like to run something as like the following:</p>

<pre><code>// Note: 'typeof' is a sample function that should return the data type.
Serial.println(typeof(myVar));
</code></pre>
 68310: <p>I tested the template form, the long-hand form, and the <code>__PRETTY_FUNCTION__</code> macro and got different results. See the last two rows of the table for the differences.</p>
<h1>Template Form 1</h1>
<p>Attribution: <a href="https://arduino.stackexchange.com/a/3080/68310">Peter Bloomfield</a></p>
<pre class="lang-cpp prettyprint-override"><code>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] )
</code></pre>
<h1>Long-Hand Form</h1>
<p>Attribution:</p>
<ol>
<li><a href="https://arduino.stackexchange.com/a/39013/68310">SnakeNET</a></li>
<li><a href="https://arduino.stackexchange.com/a/85315/68310">brewmanz</a></li>
<li>Tweaked by me to be more generic.</li>
</ol>
<pre class="lang-cpp prettyprint-override"><code>const char* TypeOf(const bool&amp;)   { static const char type[] = &quot;bool&quot;;    return type; }
const char* TypeOf(const bool*)   { static const char type[] = &quot;bool*&quot;;   return type; }
const char* TypeOf(const char&amp;)   { static const char type[] = &quot;char&quot;;    return type; }
const char* TypeOf(const char*)   { static const char type[] = &quot;char*&quot;;   return type; }
const char* TypeOf(const double&amp;) { static const char type[] = &quot;double&quot;;  return type; }
const char* TypeOf(const double*) { static const char type[] = &quot;double*&quot;; return type; }
const char* TypeOf(const float&amp;)  { static const char type[] = &quot;float&quot;;   return type; }
const char* TypeOf(const float*)  { static const char type[] = &quot;float*&quot;;  return type; }
const char* TypeOf(const int&amp;)    { static const char type[] = &quot;int&quot;;     return type; }
const char* TypeOf(const int*)    { static const char type[] = &quot;int*&quot;;    return type; }
const char* TypeOf(const String&amp;) { static const char type[] = &quot;String&quot;;  return type; }
const char* TypeOf(const String*) { static const char type[] = &quot;String*&quot;; return type; }
//const char* const TypeOf(const char[5])    { static const char* type = &quot;char[5]&quot;;    return type; }  // Decays to char* so generates compiler redefinition warning.
const char* const TypeOf(const char(*)[5]) { static const char* type = &quot;char(*)[5]&quot;; return type; }
</code></pre>
<h1>Template Form 2: <code>__PRETTY_FUNCTION__</code> Macro</h1>
<p>Attribution:</p>
<ol>
<li>Original <code>String</code> version by <a href="https://arduino.stackexchange.com/a/85398/68310">Vladimir Talybin</a></li>
<li>Tweaked <code>char</code> array version by me.</li>
</ol>
<h2>Using <code>String</code></h2>
<pre class="lang-cpp prettyprint-override"><code>template &lt;class T&gt;
String type_name(const T&amp;)
{
    String s = __PRETTY_FUNCTION__;
    int start = s.indexOf(&quot;[with T = &quot;) + 10;
    int stop = s.lastIndexOf(']');
    return s.substring(start, stop);
}
</code></pre>
<h2>Using <code>char</code> array</h2>
<p>This variation has lower memory requirements.</p>
<pre class="lang-cpp prettyprint-override"><code>template &lt;typename T&gt;
char* type_name(const T&amp;)
{
    char* pf = __PRETTY_FUNCTION__;
    char* begin = strstr(pf, &quot;[with T = &quot;) + 10;
    char* end = strrchr(pf, ']');
    *end = 0;
    return begin;
}
</code></pre>
<h1>Test Code</h1>
<pre class="lang-cpp prettyprint-override"><code>bool b = false;
char c = 'a';
double d = 1.2;
float f = 3.4;
int i = 5;
String s = &quot;6789&quot;;
char chars[] = &quot;1234&quot;;

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

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

Serial.println(F(&quot;type_name() Test&quot;));
Serial.println(type_name(b));
Serial.println(type_name(&amp;b));
Serial.println(type_name(c));
Serial.println(type_name(&amp;c));
Serial.println(type_name(d));
Serial.println(type_name(&amp;d));
Serial.println(type_name(f));
Serial.println(type_name(&amp;f));
Serial.println(type_name(i));
Serial.println(type_name(&amp;i));
Serial.println(type_name(s));
Serial.println(type_name(&amp;s));
Serial.println(type_name(chars));
Serial.println(type_name(&amp;chars));
</code></pre>
<h1>Results</h1>
<div class="s-table-container">
<table class="s-table">
<thead>
<tr>
<th style="text-align: center;">TypeOf()</th>
<th style="text-align: center;">TYPE_NAME()</th>
<th style="text-align: center;">type_name()</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">bool</td>
<td style="text-align: center;">bool</td>
<td style="text-align: center;">bool</td>
</tr>
<tr>
<td style="text-align: center;">bool*</td>
<td style="text-align: center;">bool*</td>
<td style="text-align: center;">bool*</td>
</tr>
<tr>
<td style="text-align: center;">char</td>
<td style="text-align: center;">char</td>
<td style="text-align: center;">char</td>
</tr>
<tr>
<td style="text-align: center;">char*</td>
<td style="text-align: center;">char*</td>
<td style="text-align: center;">char*</td>
</tr>
<tr>
<td style="text-align: center;">double</td>
<td style="text-align: center;">double</td>
<td style="text-align: center;">double</td>
</tr>
<tr>
<td style="text-align: center;">double*</td>
<td style="text-align: center;">double*</td>
<td style="text-align: center;">double*</td>
</tr>
<tr>
<td style="text-align: center;">float</td>
<td style="text-align: center;">float</td>
<td style="text-align: center;">float</td>
</tr>
<tr>
<td style="text-align: center;">float*</td>
<td style="text-align: center;">float*</td>
<td style="text-align: center;">float*</td>
</tr>
<tr>
<td style="text-align: center;">int</td>
<td style="text-align: center;">int</td>
<td style="text-align: center;">int</td>
</tr>
<tr>
<td style="text-align: center;">int*</td>
<td style="text-align: center;">int*</td>
<td style="text-align: center;">int*</td>
</tr>
<tr>
<td style="text-align: center;">String</td>
<td style="text-align: center;">String</td>
<td style="text-align: center;">String</td>
</tr>
<tr>
<td style="text-align: center;">String*</td>
<td style="text-align: center;">String*</td>
<td style="text-align: center;">String*</td>
</tr>
<tr>
<td style="text-align: center;">char*</td>
<td style="text-align: center;">char[5]</td>
<td style="text-align: center;">char [5]</td>
</tr>
<tr>
<td style="text-align: center;">char(*)[5]</td>
<td style="text-align: center;">char(*)[5]</td>
<td style="text-align: center;">char (*)[5]</td>
</tr>
</tbody>
</table>
</div><h1>Some Conclusions</h1>
<ol>
<li><code>TypeOf()</code> has an issue with a decaying pointer.</li>
<li><code>TypeOf()</code> and <code>TYPE_NAME()</code> have to have individual definitions with long-hand form or <code>MAKE_TYPE_INFO</code>.</li>
<li><code>type_name()</code> uses the <code>__PRETTY_FUNCTION__</code> macro to automatically get the type name within the template function, therefore there is no need to individually define the different types.</li>
<li>Template Form 1 has the lowest memory requirements.</li>
</ol>
<h1>Questions</h1>
<ol>
<li>Since Template Form 2 has higher memory requirements than Template Form 1, how could it be converted from Form 2 to Form 1?</li>
<li>How would a <code>static const char *</code> be initialised from a substring of a <code>char</code> array obtained from the <code>__PRETTY_FUNCTION__</code> macro and passed as a template argument at compile-time?</li>
</ol>
<p>Something like this pseudo code?</p>
<pre><code>static const char * name = substr(__PRETTY_FUNCTION__, begin, end);
</code></pre>
<p>Perhaps this constant expression which can be assigned to a template struct member at compile-time? (I used a <code>for</code> loop because <code>strstr</code> and <code>strrchr</code> are not <code>constexpr</code>.)</p>
<pre class="lang-cpp prettyprint-override"><code>template &lt;typename T&gt;
constexpr char* type_name(const T&amp;)
{
    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 &amp;&amp; *pf == '[')
      {
        begin = pf + 10;
        found_open_square_bracket = true;
      }
      if (*pf == ']')
      {
        end = pf;
      }
    }
    *end = 0;
    return begin;
}
</code></pre>
 78500: <p>There also a trick using compiler predefined macro. The advantage is it can print any type.</p>
<pre class="lang-cpp prettyprint-override"><code>template &lt;class T&gt;
String type_name(const T&amp;)
{   
    String s = __PRETTY_FUNCTION__;

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

    return s.substring(start, stop);
}
</code></pre>
<p>Use it like this</p>
<pre class="lang-cpp prettyprint-override"><code>double pi = 3.14;
const char* str = &quot;test&quot;;
    
Serial.println(type_name(pi));
Serial.println(type_name(str));
</code></pre>
<h2>EDIT:</h2>
<p>Some improvements based on tim's analyze.</p>
<p>First I have a comment on variation that has lower memory requirements. <code>__PRETTY_FUNCTION__</code> is a literal string (a constant) that should not be changed. So my const version that do not use heap is following.</p>
<pre class="lang-cpp prettyprint-override"><code>template &lt;size_t N&gt;
const char* extract_type(const char (&amp;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 &lt;class T&gt;
const char* type_name(const T&amp;)
{
    return extract_type(__PRETTY_FUNCTION__);
}
</code></pre>
<p>A less safe but more efficient version using the fact that offset to type we want extract is a constant (always the same). If function signature changes (like put it in a namespace) the offset will change too. But come on, set it once, write a comment and it will work forever :).</p>
<pre class="lang-cpp prettyprint-override"><code>template &lt;size_t N&gt;
const char* extract_type(const char (&amp;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 &lt;class T&gt;
const char* type_name(const T&amp;)
{
    return extract_type(__PRETTY_FUNCTION__);
}
</code></pre>
<p>While above is ok ofcourse it can be improved further. Here is a my version of constexpr function mentioned by @tim. It uses constant offset as above to keep it short. This code generates only extracted string of type, the proof is <a href="https://godbolt.org/z/4o1aev6n4" rel="nofollow noreferrer">here</a>.</p>
<pre class="lang-cpp prettyprint-override"><code>#include &lt;utility&gt;

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

template &lt;class T&gt;
const char* type_name(const T&amp;)
{
    return type_name&lt;T&gt;(
        std::make_index_sequence&lt;sizeof(__PRETTY_FUNCTION__) - 44&gt;());
}
</code></pre>
<p>The minimum requirement for this is C++14 because of sequence traits. These can be re-implemented (see definition <a href="https://en.cppreference.com/w/cpp/utility/integer_sequence" rel="nofollow noreferrer">here</a>) to adapt it to C++11.</p>
 54502: <p>Building on @snakeNET's answer (which I regard as function <strong>overload</strong> rather than <strong>polymorphism</strong>)...</p>
<p>A more generic way would be to pass in a pointer to Print, allowing any Print related class to be used (not just Serial); also pass objects by reference e.g.</p>
<pre><code>void types(Print* p, const String&amp;) { p-&gt;print(&quot;it's a String&quot;); } // for each type
...
Serial.print(&quot;Type is &quot;); types(&amp;Serial, myStr); Serial.println();
</code></pre>
<p>Note that I has changed 'println' to 'print'; again, making it more generic.</p>
 34212: <p>I use a simple stupid approach...</p>
<pre class="lang-C prettyprint-override"><code>// serial print variable type
void types(String a) { Serial.println(&quot;it's a String&quot;); }
void types(int a) { Serial.println(&quot;it's an int&quot;); }
void types(char *a) { Serial.println(&quot;it's a char*&quot;); }
void types(float a) { Serial.println(&quot;it's a float&quot;); }
void types(bool a) { Serial.println(&quot;it's a bool&quot;); }
</code></pre>
<p>This is the concept of <strong>polymorphism</strong> where <strong>multiple functions</strong> with different parameter types are created but with the <strong>same function name</strong>. During run time, the function matching the right number of arguments and argument type(s) will get called. Hope this explanation helps.</p>
 42: <p>In a typical C++ program, you would use the <code>typeid</code> operator, like this:</p>



<pre class="lang-cpp prettyprint-override"><code>std::cout &lt;&lt; typeid(myVar).name();
</code></pre>

<p><strong><em>However,</em></strong> that requires a compiler feature called Runtime Type Information (RTTI). It's disabled in the Arduino IDE, presumably because it tends to increase the runtime memory requirements of the program.</p>

<p>You can get more information about the resource cost here: <a href="https://stackoverflow.com/questions/579887/how-expensive-is-rtti">https://stackoverflow.com/questions/579887/how-expensive-is-rtti</a></p>

<p>Any fully-featured C++ compiler will definitely support RTTI though. If you want to try using a 3rd party IDE (such as Eclipse with the Arduino plugin) then you could easily enable it. It's probably not worth the hassle just for that though.</p>

<hr>

<p><strong>Alternative</strong><br>
A higher-performing (but less flexible) solution would be to use a traits class approach. This involves some funky template meta-programming:</p>

<pre class="lang-cpp prettyprint-override"><code>// Generic catch-all implementation.
template &lt;typename T_ty&gt; struct TypeInfo { static const char * name; };
template &lt;typename T_ty&gt; const char * TypeInfo&lt;T_ty&gt;::name = "unknown";

// Handy macro to make querying stuff easier.
#define TYPE_NAME(var) TypeInfo&lt; typeof(var) &gt;::name

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

// Type-specific implementations.
MAKE_TYPE_INFO( int )
MAKE_TYPE_INFO( float )
MAKE_TYPE_INFO( short )
</code></pre>

<p>You can add <code>MAKE_TYPE_INFO(..)</code> lines for any type you want, including the names of custom classes. You could then use it like this:</p>

<pre class="lang-cpp prettyprint-override"><code>int myVar = 17;
Serial.println( TYPE_NAME(myVar) );
</code></pre>

<p>Anything you don't define using <code>MAKE_TYPE_INFO(..)</code> will show up as <code>"unknown"</code>.</p>

<p>That's some fairly advanced stuff in there, so I won't try to explain how it all works here. There are various tutorials on the web though about C++ template programming if you're interested.</p>

<p>EDIT: It's worth noting that the <code>typeof</code> operator isn't standard C++, but is supported by a few compilers, such as GCC. It's basically an older equivalent of <code>decltype</code>, which appears in the C++11 standard.</p>
