В стандартном заголовке typeinfo библиотеки STD имеется инструмент typeid, через который можно получать значения типов переменных или выражений в виде строки. Узнать наименование типа переменной можно через метод name() следующим образом:
#include <iostream>
#include <typeinfo>
 
int main()
{
    short x(3);
    short y(6);
    std::cout << typeid(x).name() << " " << x << std::endl;
	// Итоговый тип данных в выражении x + y
    std::cout << typeid(x + y).name() << " " << x + y << std::endl;
 
    return 0;
}
Результат в GCC будет:
s 3
i 9
А в MSVC результат будет другим:
short 3
int 9
То же самое справедливо и для типов, создаваемых пользователем:
#include <iostream>
#include <typeinfo>
struct Base { virtual ~Base() = default; };
struct Derived : Base {};
 
int main() {
    Base b1;
    Derived d1;
 
    const Base *pb = &b1;
    std::cout << typeid(*pb).name() << '\n';
    pb = &d1;
    std::cout << typeid(*pb).name() << '\n';
}
Результат в GCC:
4Base 
7Derived
Результат в MSVC:
struct Base
struct Derived
Точно так же данный код будет работать на типах классов. Если структуры заменить на классы вот таким образом:
class Base {
public:
  Base() {};
  virtual ~Base() {};
};
class Derived : public Base {
public:
  Derived() {};
  virtual ~Derived() {};
};
То результат будет аналогичный.
В любом случае следует помнить, что если пользоваться typeid стандартной библиотеки STD, то итоговый код будет не кроссплатформенным. Вот такие неожиданности кроются в стандартных библиотеках, создаваемых разными вендорами.