Question

For example:

int a = 12;
cout << typeof(a) << endl;

Expected output:

int
Was it helpful?

Solution

C++11 update to a very old question: Print variable type in C++.

The accepted (and good) answer is to use typeid(a).name(), where a is a variable name.

Now in C++11 we have decltype(x), which can turn an expression into a type. And decltype() comes with its own set of very interesting rules. For example decltype(a) and decltype((a)) will generally be different types (and for good and understandable reasons once those reasons are exposed).

Will our trusty typeid(a).name() help us explore this brave new world?

No.

But the tool that will is not that complicated. And it is that tool which I am using as an answer to this question. I will compare and contrast this new tool to typeid(a).name(). And this new tool is actually built on top of typeid(a).name().

The fundamental issue:

typeid(a).name()

throws away cv-qualifiers, references, and lvalue/rvalue-ness. For example:

const int ci = 0;
std::cout << typeid(ci).name() << '\n';

For me outputs:

i

and I'm guessing on MSVC outputs:

int

I.e. the const is gone. This is not a QOI (Quality Of Implementation) issue. The standard mandates this behavior.

What I'm recommending below is:

template <typename T> std::string type_name();

which would be used like this:

const int ci = 0;
std::cout << type_name<decltype(ci)>() << '\n';

and for me outputs:

int const

<disclaimer> I have not tested this on MSVC. </disclaimer> But I welcome feedback from those who do.

The C++11 Solution

I am using __cxa_demangle for non-MSVC platforms as recommend by ipapadop in his answer to demangle types. But on MSVC I'm trusting typeid to demangle names (untested). And this core is wrapped around some simple testing that detects, restores and reports cv-qualifiers and references to the input type.

#include <type_traits>
#include <typeinfo>
#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <class T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
}

The Results

With this solution I can do this:

int& foo_lref();
int&& foo_rref();
int foo_value();

int
main()
{
    int i = 0;
    const int ci = 0;
    std::cout << "decltype(i) is " << type_name<decltype(i)>() << '\n';
    std::cout << "decltype((i)) is " << type_name<decltype((i))>() << '\n';
    std::cout << "decltype(ci) is " << type_name<decltype(ci)>() << '\n';
    std::cout << "decltype((ci)) is " << type_name<decltype((ci))>() << '\n';
    std::cout << "decltype(static_cast<int&>(i)) is " << type_name<decltype(static_cast<int&>(i))>() << '\n';
    std::cout << "decltype(static_cast<int&&>(i)) is " << type_name<decltype(static_cast<int&&>(i))>() << '\n';
    std::cout << "decltype(static_cast<int>(i)) is " << type_name<decltype(static_cast<int>(i))>() << '\n';
    std::cout << "decltype(foo_lref()) is " << type_name<decltype(foo_lref())>() << '\n';
    std::cout << "decltype(foo_rref()) is " << type_name<decltype(foo_rref())>() << '\n';
    std::cout << "decltype(foo_value()) is " << type_name<decltype(foo_value())>() << '\n';
}

and the output is:

decltype(i) is int
decltype((i)) is int&
decltype(ci) is int const
decltype((ci)) is int const&
decltype(static_cast<int&>(i)) is int&
decltype(static_cast<int&&>(i)) is int&&
decltype(static_cast<int>(i)) is int
decltype(foo_lref()) is int&
decltype(foo_rref()) is int&&
decltype(foo_value()) is int

Note (for example) the difference between decltype(i) and decltype((i)). The former is the type of the declaration of i. The latter is the "type" of the expression i. (expressions never have reference type, but as a convention decltype represents lvalue expressions with lvalue references).

Thus this tool is an excellent vehicle just to learn about decltype, in addition to exploring and debugging your own code.

In contrast, if I were to build this just on typeid(a).name(), without adding back lost cv-qualifiers or references, the output would be:

decltype(i) is int
decltype((i)) is int
decltype(ci) is int
decltype((ci)) is int
decltype(static_cast<int&>(i)) is int
decltype(static_cast<int&&>(i)) is int
decltype(static_cast<int>(i)) is int
decltype(foo_lref()) is int
decltype(foo_rref()) is int
decltype(foo_value()) is int

I.e. Every reference and cv-qualifier is stripped off.

C++14 Update

Just when you think you've got a solution to a problem nailed, someone always comes out of nowhere and shows you a much better way. :-)

This answer from Jamboree shows how to get the type name in C++14 at compile time. It is a brilliant solution for a couple reasons:

  1. It's at compile time!
  2. You get the compiler itself to do the job instead of a library (even a std::lib). This means more accurate results for the latest language features (like lambdas).

Jamboree's answer doesn't quite lay everything out for VS, and I'm tweaking his code a little bit. But since this answer gets a lot of views, take some time to go over there and upvote his answer, without which, this update would never have happened.

#include <cstddef>
#include <stdexcept>
#include <cstring>
#include <ostream>

#ifndef _MSC_VER
#  if __cplusplus < 201103
#    define CONSTEXPR11_TN
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN
#  elif __cplusplus < 201402
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN noexcept
#  else
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN constexpr
#    define NOEXCEPT_TN noexcept
#  endif
#else  // _MSC_VER
#  if _MSC_VER < 1900
#    define CONSTEXPR11_TN
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN
#  elif _MSC_VER < 2000
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN noexcept
#  else
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN constexpr
#    define NOEXCEPT_TN noexcept
#  endif
#endif  // _MSC_VER

class static_string
{
    const char* const p_;
    const std::size_t sz_;

public:
    typedef const char* const_iterator;

    template <std::size_t N>
    CONSTEXPR11_TN static_string(const char(&a)[N]) NOEXCEPT_TN
        : p_(a)
        , sz_(N-1)
        {}

    CONSTEXPR11_TN static_string(const char* p, std::size_t N) NOEXCEPT_TN
        : p_(p)
        , sz_(N)
        {}

    CONSTEXPR11_TN const char* data() const NOEXCEPT_TN {return p_;}
    CONSTEXPR11_TN std::size_t size() const NOEXCEPT_TN {return sz_;}

    CONSTEXPR11_TN const_iterator begin() const NOEXCEPT_TN {return p_;}
    CONSTEXPR11_TN const_iterator end()   const NOEXCEPT_TN {return p_ + sz_;}

    CONSTEXPR11_TN char operator[](std::size_t n) const
    {
        return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
    }
};

inline
std::ostream&
operator<<(std::ostream& os, static_string const& s)
{
    return os.write(s.data(), s.size());
}

template <class T>
CONSTEXPR14_TN
static_string
type_name()
{
#ifdef __clang__
    static_string p = __PRETTY_FUNCTION__;
    return static_string(p.data() + 31, p.size() - 31 - 1);
#elif defined(__GNUC__)
    static_string p = __PRETTY_FUNCTION__;
#  if __cplusplus < 201402
    return static_string(p.data() + 36, p.size() - 36 - 1);
#  else
    return static_string(p.data() + 46, p.size() - 46 - 1);
#  endif
#elif defined(_MSC_VER)
    static_string p = __FUNCSIG__;
    return static_string(p.data() + 38, p.size() - 38 - 7);
#endif
}

This code will auto-backoff on the constexpr if you're still stuck in ancient C++11. And if you're painting on the cave wall with C++98/03, the noexcept is sacrificed as well.

C++17 Update

In the comments below Lyberta points out that the new std::string_view can replace static_string:

template <class T>
constexpr
std::string_view
type_name()
{
    using namespace std;
#ifdef __clang__
    string_view p = __PRETTY_FUNCTION__;
    return string_view(p.data() + 34, p.size() - 34 - 1);
#elif defined(__GNUC__)
    string_view p = __PRETTY_FUNCTION__;
#  if __cplusplus < 201402
    return string_view(p.data() + 36, p.size() - 36 - 1);
#  else
    return string_view(p.data() + 49, p.find(';', 49) - 49);
#  endif
#elif defined(_MSC_VER)
    string_view p = __FUNCSIG__;
    return string_view(p.data() + 84, p.size() - 84 - 7);
#endif
}

I've updated the constants for VS thanks to the very nice detective work by Jive Dadson in the comments below.

OTHER TIPS

Try:

#include <typeinfo>

// …
std::cout << typeid(a).name() << '\n';

You might have to activate RTTI in your compiler options for this to work. Additionally, the output of this depends on the compiler. It might be a raw type name or a name mangling symbol or anything in between.

Very ugly but does the trick if you only want compile time info (e.g. for debugging):

auto testVar = std::make_tuple(1, 1.0, "abc");
decltype(testVar)::foo = 1;

Returns:

Compilation finished with errors:
source.cpp: In function 'int main()':
source.cpp:5:19: error: 'dummy_error' is not a member of 'std::tuple<int, double, const char*>'

Don't forget to include <typeinfo>

I believe what you are referring to is runtime type identification. You can achieve the above by doing .

#include <iostream>
#include <typeinfo>

using namespace std;

int main() {
  int i;
  cout << typeid(i).name();
  return 0;
}

Note that the names generated by the RTTI feature of C++ is not portable. For example, the class

MyNamespace::CMyContainer<int, test_MyNamespace::CMyObject>

will have the following names:

// MSVC 2003:
class MyNamespace::CMyContainer[int,class test_MyNamespace::CMyObject]
// G++ 4.2:
N8MyNamespace8CMyContainerIiN13test_MyNamespace9CMyObjectEEE

So you can't use this information for serialization. But still, the typeid(a).name() property can still be used for log/debug purposes

You can use templates.

template <typename T> const char* typeof(T&) { return "unknown"; }    // default
template<> const char* typeof(int&) { return "int"; }
template<> const char* typeof(float&) { return "float"; }

In the example above, when the type is not matched it will print "unknown".

As mentioned, typeid().name() may return a mangled name. In GCC (and some other compilers) you can work around it with the following code:

#include <cxxabi.h>
#include <iostream>
#include <typeinfo>
#include <cstdlib>

namespace some_namespace { namespace another_namespace {

  class my_class { };

} }

int main() {
  typedef some_namespace::another_namespace::my_class my_type;
  // mangled
  std::cout << typeid(my_type).name() << std::endl;

  // unmangled
  int status = 0;
  char* demangled = abi::__cxa_demangle(typeid(my_type).name(), 0, 0, &status);

  switch (status) {
    case -1: {
      // could not allocate memory
      std::cout << "Could not allocate memory" << std::endl;
      return -1;
    } break;
    case -2: {
      // invalid name under the C++ ABI mangling rules
      std::cout << "Invalid name" << std::endl;
      return -1;
    } break;
    case -3: {
      // invalid argument
      std::cout << "Invalid argument to demangle()" << std::endl;
      return -1;
    } break;
 }
 std::cout << demangled << std::endl;

 free(demangled);

 return 0;

}

You could use a traits class for this. Something like:

#include <iostream>
using namespace std;

template <typename T> class type_name {
public:
    static const char *name;
};

#define DECLARE_TYPE_NAME(x) template<> const char *type_name<x>::name = #x;
#define GET_TYPE_NAME(x) (type_name<typeof(x)>::name)

DECLARE_TYPE_NAME(int);

int main()
{
    int a = 12;
    cout << GET_TYPE_NAME(a) << endl;
}

The DECLARE_TYPE_NAME define exists to make your life easier in declaring this traits class for all the types you expect to need.

This might be more useful than the solutions involving typeid because you get to control the output. For example, using typeid for long long on my compiler gives "x".

In C++11, we have decltype. There is no way in standard c++ to display exact type of variable declared using decltype. We can use boost typeindex i.e type_id_with_cvr (cvr stands for const, volatile, reference) to print type like below.

#include <iostream>
#include <boost/type_index.hpp>

using namespace std;
using boost::typeindex::type_id_with_cvr;

int main() {
  int i = 0;
  const int ci = 0;
  cout << "decltype(i) is " << type_id_with_cvr<decltype(i)>().pretty_name() << '\n';
  cout << "decltype((i)) is " << type_id_with_cvr<decltype((i))>().pretty_name() << '\n';
  cout << "decltype(ci) is " << type_id_with_cvr<decltype(ci)>().pretty_name() << '\n';
  cout << "decltype((ci)) is " << type_id_with_cvr<decltype((ci))>().pretty_name() << '\n';
  cout << "decltype(std::move(i)) is " << type_id_with_cvr<decltype(std::move(i))>().pretty_name() << '\n';
  cout << "decltype(std::static_cast<int&&>(i)) is " << type_id_with_cvr<decltype(static_cast<int&&>(i))>().pretty_name() << '\n';
  return 0;
}

The other answers involving RTTI (typeid) are probably what you want, as long as:

  • you can afford the memory overhead (which can be considerable with some compilers)
  • the class names your compiler returns are useful

The alternative, (similar to Greg Hewgill's answer), is to build a compile-time table of traits.

template <typename T> struct type_as_string;

// declare your Wibble type (probably with definition of Wibble)
template <>
struct type_as_string<Wibble>
{
    static const char* const value = "Wibble";
};

Be aware that if you wrap the declarations in a macro, you'll have trouble declaring names for template types taking more than one parameter (e.g. std::map), due to the comma.

To access the name of the type of a variable, all you need is

template <typename T>
const char* get_type_as_string(const T&)
{
    return type_as_string<T>::value;
}

A more generic solution without function overloading than my previous one:

template<typename T>
std::string TypeOf(T){
    std::string Type="unknown";
    if(std::is_same<T,int>::value) Type="int";
    if(std::is_same<T,std::string>::value) Type="String";
    if(std::is_same<T,MyClass>::value) Type="MyClass";

    return Type;}

Here MyClass is user defined class. More conditions can be added here as well.

Example:

#include <iostream>



class MyClass{};


template<typename T>
std::string TypeOf(T){
    std::string Type="unknown";
    if(std::is_same<T,int>::value) Type="int";
    if(std::is_same<T,std::string>::value) Type="String";
    if(std::is_same<T,MyClass>::value) Type="MyClass";
    return Type;}


int main(){;
    int a=0;
    std::string s="";
    MyClass my;
    std::cout<<TypeOf(a)<<std::endl;
    std::cout<<TypeOf(s)<<std::endl;
    std::cout<<TypeOf(my)<<std::endl;

    return 0;}

Output:

int
String
MyClass

I like Nick's method, A complete form might be this (for all basic data types):

template <typename T> const char* typeof(T&) { return "unknown"; }    // default
template<> const char* typeof(int&) { return "int"; }
template<> const char* typeof(short&) { return "short"; }
template<> const char* typeof(long&) { return "long"; }
template<> const char* typeof(unsigned&) { return "unsigned"; }
template<> const char* typeof(unsigned short&) { return "unsigned short"; }
template<> const char* typeof(unsigned long&) { return "unsigned long"; }
template<> const char* typeof(float&) { return "float"; }
template<> const char* typeof(double&) { return "double"; }
template<> const char* typeof(long double&) { return "long double"; }
template<> const char* typeof(std::string&) { return "String"; }
template<> const char* typeof(char&) { return "char"; }
template<> const char* typeof(signed char&) { return "signed char"; }
template<> const char* typeof(unsigned char&) { return "unsigned char"; }
template<> const char* typeof(char*&) { return "char*"; }
template<> const char* typeof(signed char*&) { return "signed char*"; }
template<> const char* typeof(unsigned char*&) { return "unsigned char*"; }

You may also use c++filt with option -t (type) to demangle the type name:

#include <iostream>
#include <typeinfo>
#include <string>

using namespace std;

int main() {
  auto x = 1;
  string my_type = typeid(x).name();
  system(("echo " + my_type + " | c++filt -t").c_str());
  return 0;
}

Tested on linux only.

As I challenge I decided to test how far can one go with platform-independent (hopefully) template trickery.

The names are assembled completely at compilation time. (Which means typeid(T).name() couldn't be used, thus you have to explicitly provide names for non-compound types. Otherwise placeholders will be displayed instead.)

Example usage:

TYPE_NAME(int)
TYPE_NAME(void)
// You probably should list all primitive types here.

TYPE_NAME(std::string)

int main()
{
    // A simple case
    std::cout << type_name<void(*)(int)> << '\n';
    // -> `void (*)(int)`

    // Ugly mess case
    // Note that compiler removes cv-qualifiers from parameters and replaces arrays with pointers.
    std::cout << type_name<void (std::string::*(int[3],const int, void (*)(std::string)))(volatile int*const*)> << '\n';
    // -> `void (std::string::*(int *,int,void (*)(std::string)))(volatile int *const*)`

    // A case with undefined types
    //  If a type wasn't TYPE_NAME'd, it's replaced by a placeholder, one of `class?`, `union?`, `enum?` or `??`.
    std::cout << type_name<std::ostream (*)(int, short)> << '\n';
    // -> `class? (*)(int,??)`
    // With appropriate TYPE_NAME's, the output would be `std::string (*)(int,short)`.
}

Code:

#include <type_traits>
#include <utility>

static constexpr std::size_t max_str_lit_len = 256;

template <std::size_t I, std::size_t N> constexpr char sl_at(const char (&str)[N])
{
    if constexpr(I < N)
        return str[I];
    else
        return '\0';
}

constexpr std::size_t sl_len(const char *str)
{
    for (std::size_t i = 0; i < max_str_lit_len; i++)
        if (str[i] == '\0')
            return i;
    return 0;
}

template <char ...C> struct str_lit
{
    static constexpr char value[] {C..., '\0'};
    static constexpr int size = sl_len(value);

    template <typename F, typename ...P> struct concat_impl {using type = typename concat_impl<F>::type::template concat_impl<P...>::type;};
    template <char ...CC> struct concat_impl<str_lit<CC...>> {using type = str_lit<C..., CC...>;};
    template <typename ...P> using concat = typename concat_impl<P...>::type;
};

template <typename, const char *> struct trim_str_lit_impl;
template <std::size_t ...I, const char *S> struct trim_str_lit_impl<std::index_sequence<I...>, S>
{
    using type = str_lit<S[I]...>;
};
template <std::size_t N, const char *S> using trim_str_lit = typename trim_str_lit_impl<std::make_index_sequence<N>, S>::type;

#define STR_LIT(str) ::trim_str_lit<::sl_len(str), ::str_lit<STR_TO_VA(str)>::value>
#define STR_TO_VA(str) STR_TO_VA_16(str,0),STR_TO_VA_16(str,16),STR_TO_VA_16(str,32),STR_TO_VA_16(str,48)
#define STR_TO_VA_16(str,off) STR_TO_VA_4(str,0+off),STR_TO_VA_4(str,4+off),STR_TO_VA_4(str,8+off),STR_TO_VA_4(str,12+off)
#define STR_TO_VA_4(str,off) ::sl_at<off+0>(str),::sl_at<off+1>(str),::sl_at<off+2>(str),::sl_at<off+3>(str)

template <char ...C> constexpr str_lit<C...> make_str_lit(str_lit<C...>) {return {};}
template <std::size_t N> constexpr auto make_str_lit(const char (&str)[N])
{
    return trim_str_lit<sl_len((const char (&)[N])str), str>{};
}

template <std::size_t A, std::size_t B> struct cexpr_pow {static constexpr std::size_t value = A * cexpr_pow<A,B-1>::value;};
template <std::size_t A> struct cexpr_pow<A,0> {static constexpr std::size_t value = 1;};
template <std::size_t N, std::size_t X, typename = std::make_index_sequence<X>> struct num_to_str_lit_impl;
template <std::size_t N, std::size_t X, std::size_t ...Seq> struct num_to_str_lit_impl<N, X, std::index_sequence<Seq...>>
{
    static constexpr auto func()
    {
        if constexpr (N >= cexpr_pow<10,X>::value)
            return num_to_str_lit_impl<N, X+1>::func();
        else
            return str_lit<(N / cexpr_pow<10,X-1-Seq>::value % 10 + '0')...>{};
    }
};
template <std::size_t N> using num_to_str_lit = decltype(num_to_str_lit_impl<N,1>::func());


using spa = str_lit<' '>;
using lpa = str_lit<'('>;
using rpa = str_lit<')'>;
using lbr = str_lit<'['>;
using rbr = str_lit<']'>;
using ast = str_lit<'*'>;
using amp = str_lit<'&'>;
using con = str_lit<'c','o','n','s','t'>;
using vol = str_lit<'v','o','l','a','t','i','l','e'>;
using con_vol = con::concat<spa, vol>;
using nsp = str_lit<':',':'>;
using com = str_lit<','>;
using unk = str_lit<'?','?'>;

using c_cla = str_lit<'c','l','a','s','s','?'>;
using c_uni = str_lit<'u','n','i','o','n','?'>;
using c_enu = str_lit<'e','n','u','m','?'>;

template <typename T> inline constexpr bool ptr_or_ref = std::is_pointer_v<T> || std::is_reference_v<T> || std::is_member_pointer_v<T>;
template <typename T> inline constexpr bool func_or_arr = std::is_function_v<T> || std::is_array_v<T>;

template <typename T> struct primitive_type_name {using value = unk;};

template <typename T, typename = std::enable_if_t<std::is_class_v<T>>> using enable_if_class = T;
template <typename T, typename = std::enable_if_t<std::is_union_v<T>>> using enable_if_union = T;
template <typename T, typename = std::enable_if_t<std::is_enum_v <T>>> using enable_if_enum  = T;
template <typename T> struct primitive_type_name<enable_if_class<T>> {using value = c_cla;};
template <typename T> struct primitive_type_name<enable_if_union<T>> {using value = c_uni;};
template <typename T> struct primitive_type_name<enable_if_enum <T>> {using value = c_enu;};

template <typename T> struct type_name_impl;

template <typename T> using type_name_lit = std::conditional_t<std::is_same_v<typename primitive_type_name<T>::value::template concat<spa>,
                                                                               typename type_name_impl<T>::l::template concat<typename type_name_impl<T>::r>>,
                                            typename primitive_type_name<T>::value,
                                            typename type_name_impl<T>::l::template concat<typename type_name_impl<T>::r>>;
template <typename T> inline constexpr const char *type_name = type_name_lit<T>::value;

template <typename T, typename = std::enable_if_t<!std::is_const_v<T> && !std::is_volatile_v<T>>> using enable_if_no_cv = T;

template <typename T> struct type_name_impl
{
    using l = typename primitive_type_name<T>::value::template concat<spa>;
    using r = str_lit<>;
};
template <typename T> struct type_name_impl<const T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<con>,
                                 con::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<volatile T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<vol>,
                                 vol::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<const volatile T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<con_vol>,
                                 con_vol::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<T *>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, ast>,
                                 typename type_name_impl<T>::l::template concat<     ast>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T &>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, amp>,
                                 typename type_name_impl<T>::l::template concat<     amp>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T &&>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, amp, amp>,
                                 typename type_name_impl<T>::l::template concat<     amp, amp>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T, typename C> struct type_name_impl<T C::*>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, type_name_lit<C>, nsp, ast>,
                                 typename type_name_impl<T>::l::template concat<     type_name_lit<C>, nsp, ast>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<enable_if_no_cv<T[]>>
{
    using l = typename type_name_impl<T>::l;
    using r = lbr::concat<rbr, typename type_name_impl<T>::r>;
};
template <typename T, std::size_t N> struct type_name_impl<enable_if_no_cv<T[N]>>
{
    using l = typename type_name_impl<T>::l;
    using r = lbr::concat<num_to_str_lit<N>, rbr, typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T()>
{
    using l = typename type_name_impl<T>::l;
    using r = lpa::concat<rpa, typename type_name_impl<T>::r>;
};
template <typename T, typename P1, typename ...P> struct type_name_impl<T(P1, P...)>
{
    using l = typename type_name_impl<T>::l;
    using r = lpa::concat<type_name_lit<P1>,
                          com::concat<type_name_lit<P>>..., rpa, typename type_name_impl<T>::r>;
};

#define TYPE_NAME(t) template <> struct primitive_type_name<t> {using value = STR_LIT(#t);};
#include <iostream>
#include <typeinfo>
using namespace std;
#define show_type_name(_t) \
    system(("echo " + string(typeid(_t).name()) + " | c++filt -t").c_str())

int main() {
    auto a = {"one", "two", "three"};
    cout << "Type of a: " << typeid(a).name() << endl;
    cout << "Real type of a:\n";
    show_type_name(a);
    for (auto s : a) {
        if (string(s) == "one") {
            cout << "Type of s: " << typeid(s).name() << endl;
            cout << "Real type of s:\n";
            show_type_name(s);
        }
        cout << s << endl;
    }

    int i = 5;
    cout << "Type of i: " << typeid(i).name() << endl;
    cout << "Real type of i:\n";
    show_type_name(i);
    return 0;
}

Output:

Type of a: St16initializer_listIPKcE
Real type of a:
std::initializer_list<char const*>
Type of s: PKc
Real type of s:
char const*
one
two
three
Type of i: i
Real type of i:
int

As explained by Scott Meyers in Effective Modern C++,

Calls to std::type_info::name are not guaranteed to return anythong sensible.

The best solution is to let the compiler generate an error message during the type deduction, for example,

template<typename T>
class TD;

int main(){
    const int theAnswer = 32;
    auto x = theAnswer;
    auto y = &theAnswer;
    TD<decltype(x)> xType;
    TD<decltype(y)> yType;
    return 0;
}

The result will be something like this, depending on the compilers,

test4.cpp:10:21: error: aggregate ‘TD<int> xType’ has incomplete type and cannot be defined TD<decltype(x)> xType;

test4.cpp:11:21: error: aggregate ‘TD<const int *> yType’ has incomplete type and cannot be defined TD<decltype(y)> yType;

Hence, we get to know that x's type is int, y's type is const int*

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top