621

For example:

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

Expected output:

int
ThePCWizard
  • 3,338
  • 2
  • 21
  • 32
Jorge Ferreira
  • 96,051
  • 25
  • 122
  • 132
  • 3
    Here is a summary of Howard's long form solution but implemented with a heretical one-line macro: `#define DEMANGLE_TYPEID_NAME(x) abi::__cxa_demangle(typeid((x)).name(), NULL, NULL, NULL)`. If you need cross-platform support: Use `#ifdef`, `#else`, `#endif` to provide one macros for other platforms like MSVC. – Trevor Boyd Smith Jul 05 '16 at 15:01
  • With more explicit human readable requirement: http://stackoverflow.com/questions/12877521/human-readable-type-info-name – Ciro Santilli OurBigBook.com Jul 22 '16 at 22:05
  • 3
    If you only use this for debugging you might want to consider `template void print_T() { std::cout << __PRETTY_FUNCTION__ << '\n'; }`. Then using e.g. `print_T();` will print `void print_T() [T = const int *const **]` at runtime and preserves all the qualifiers (works in GCC and Clang). – Henri Menke May 29 '17 at 06:01
  • @Henri, `__PRETTY_FUNCTION__` is not Standard C++ (requirement is in the question title). – Toby Speight Feb 13 '20 at 09:44

25 Answers25

777

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.

Update:

Be sure to check out this rewrite or this rewrite below which eliminate the unreadable magic numbers in my latest formulation.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 4
    VS 14 CTP printed out correct types, I only had to add one `#include ` line. – Massimiliano Dec 21 '14 at 07:02
  • 3
    Why templatestd::string type_name()? Why are you not passing a type as an argument? –  Dec 31 '15 at 00:05
  • 2
    I believe my rationale was that sometimes I *only* had a type (such as a deduced template parameter), and I didn't want to have to artificially construct one of those to get the type (though these days `declval` would do the job). – Howard Hinnant Dec 31 '15 at 02:29
  • 1
    Sorry for stupid and lame question but will this work with member types of class types? Thanks – Angelus Mortis Mar 02 '16 at 18:14
  • 8
    @AngelusMortis: Because English is vague/ambiguous compared to C++ code, I encourage you to copy/paste this into your test case with the specific type you are interested in, and with the specific compiler you are interested in, and write back with more details if the result is surprising and/or unsatisfactory. – Howard Hinnant Mar 02 '16 at 22:20
  • 3
    @HowardHinnant can you use `std::string_view` instead of `static_string`? –  Aug 09 '17 at 20:16
  • @Lyberta: Works for me! Good observation. It also looks like clang has adjusted the output of `__PRETTY_FUNCTION__` since I wrote this. Use `34` in place of `31` under `__clang__`. – Howard Hinnant Aug 09 '17 at 20:40
  • @Howard - After changing static_string to string_view, the magic numbers are wrong. Under VC++17, change 38 to 84 in both places. – Jive Dadson Nov 04 '17 at 23:09
  • @Howard - You're welcome. The numbers for other compilers are probably different also. I have no way to test those. – Jive Dadson Nov 04 '17 at 23:22
  • 1
    Worth pointing out: the magic numbers in this function will change if you put the function into a namespace. I rewrote it with copies of the output string (literal) and a constexpr `length` function to avoid having to chase fencepost errors. – aghast Feb 20 '18 at 05:02
  • Interesting, thanks for the update. Sounds like the VS2017 `typeid(TR).name()` is radically different from previous versions. – Howard Hinnant Aug 11 '18 at 15:28
  • GCC-9 says: `the value of '__PRETTY_FUNCTION__' is not usable in a constant expression` I'm not sure if it is a bug or intent – hutorny Oct 07 '18 at 17:36
  • @hutorny That's interesting. It sounds like a regression to me. – Howard Hinnant Oct 07 '18 at 23:14
  • @HowardHinnant - my wrong :). I used `return string_view(__PRETTY_FUNCTION__);` and it worked till v8.2. With GCC 9.0 `constexpr string_view pretty = __PRETTY_FUNCTION__; return pretty;` works as expected – hutorny Oct 08 '18 at 06:49
  • Just to bring everyone's attention to `__PRETTY_FUNCTION__` caveat - compilers store entire string, not just the substring - if you inspect an example here: https://godbolt.org/z/peqQ9O or run it there: https://wandbox.org/nojs/gcc-head/permlink/vUd1rNLX04ClIRic, you'll see original long strings. To store only what is needed, substrings should be compile-time copied. Possible solution is here: https://wandbox.org/nojs/gcc-head/permlink/wxvacbsEQUZRvUZ5 – hutorny Oct 08 '18 at 09:14
  • _"The standard mandates this behavior."_ While technically true because you're talking about how the expression is handled, this statement could be deemed a little misleading because, speaking far more generally, the standard mandates _nothing_ about the return value of `name()`. Food for thought. – Lightness Races in Orbit Nov 02 '18 at 11:25
  • 1
    Man I have been looking for the source where did I implement this ``type_name`` trick on *for nearly a full year*, I should've known it had to be someone at StackOverflow. I'm gonna credit you in my library later, not to mention integrate your addition of uing ``string_view`` instead. – Luis Machuca Dec 16 '18 at 20:15
  • Better use `decltype(std::free)`, a custom function-object-class, or a lambda instead of `void(*)(void*)`, as the *exact* type of `std::free` need not be `void(*)(void*)`. `std::integral_constant` would also work. – Deduplicator Dec 25 '18 at 10:02
  • 1
    Magic numbers are prone to changing. Function https://stackoverflow.com/a/58331141/7163942 calculates prefix/suffix and corresponding magic numbers automatically. – Val Sep 03 '20 at 11:54
  • Agreed. https://stackoverflow.com/a/58331141/576911 is my current favorite too (and upvoted). – Howard Hinnant Sep 03 '20 at 13:50
  • HowardHinnant: Could you have a look at another [take](https://stackoverflow.com/a/64490578/1593077) on avoiding the magic numbers? – einpoklum Oct 22 '20 at 21:38
  • @einpoklum Very nice! – Howard Hinnant Oct 23 '20 at 02:15
  • Hi, just as a follow-up, in c++11 version, simply appending reference marks may not be 100% correct, like `int (&) [100]` will output something like `int [100]&`, later version is very brilliant! – SHP Mar 11 '21 at 10:53
  • can I ask why C++ get rid of C `typeof`? – Egon Stetmann. Apr 20 '21 at 19:02
  • `typeof` was (is?) a gcc extension. When the committee standardized it in C++11, it was renamed `decltype` and given slightly different behavior. – Howard Hinnant Apr 20 '21 at 21:20
  • Good work. It would be nice to reorder C++ standards: C++17 (or even 20) goes first. So people could read the most relevant standard first, and the look for the history of solutions. – kyb Aug 02 '21 at 10:17
  • This answer is excellent, thank you! For others running into the same issue as me: Note that the hard-coded indices will be an issue if you put this function in a namespace. – qz- Sep 03 '21 at 00:03
  • Also isn't the `#if __cplusplus < 201402` check obsolete? – qz- Sep 03 '21 at 00:04
  • Note: In C++20, using `std::unique_ptr` with `std::free` is (technically) UB as you use it because it takes the address of the function. – Quirin F. Schroll Dec 27 '22 at 23:33
  • Fortunately that's just for the C++11 solution. :-) – Howard Hinnant Dec 28 '22 at 01:41
269

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.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 7
    Why string returned by name() function is implementation defined? – Destructor Sep 02 '15 at 12:41
  • 7
    @PravasiMeet No good reason, as far as I know. The committee simply didn't want to force compiler implementors into particular technical directions — probably a mistake, in hindsight. – Konrad Rudolph Sep 02 '15 at 14:52
  • 2
    Is there a flag I could use to enable RTTI? Maybe you could make your answer inclusive. – Jim Jan 13 '16 at 16:22
  • 7
    @Destructor Providing a standardized name mangling format might give the impression that interoperability between binaries built by two different compilers is possible and/or safe, when it is not. Because C++ does not have a standard ABI, a standard name mangling scheme would be pointless, and potentially misleading and dangerous. – Elkvis May 05 '16 at 15:52
  • 2
    @Jim The section on compiler flags would be an order of magnitude longer than the answer itself. GCC compiles with it on by default, hence "-fno-rtti", other compilers may choose not to, but there is no standard for compiler flags. – kfsone Oct 03 '16 at 22:42
155

According to Howard's solution, if you don't like the magic number, I think this is a good way to represent and it looks intuitive:

#include <string_view>

template <typename T>
constexpr auto type_name() {
  std::string_view name, prefix, suffix;
#ifdef __clang__
  name = __PRETTY_FUNCTION__;
  prefix = "auto type_name() [T = ";
  suffix = "]";
#elif defined(__GNUC__)
  name = __PRETTY_FUNCTION__;
  prefix = "constexpr auto type_name() [with T = ";
  suffix = "]";
#elif defined(_MSC_VER)
  name = __FUNCSIG__;
  prefix = "auto __cdecl type_name<";
  suffix = ">(void)";
#endif
  name.remove_prefix(prefix.size());
  name.remove_suffix(suffix.size());
  return name;
}

Demo.

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • 11
    This is a great distillation of efforts over the past several C++ versions into something that's short and sweet. +1. – einpoklum Dec 27 '19 at 18:32
  • 7
    This is my favorite too! – Howard Hinnant Jan 03 '20 at 23:02
  • 2
    Here a similar function that I use, that detects the suffix/prefix automatically: https://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template/59522794#59522794 – HolyBlackCat Jan 05 '20 at 19:40
  • This does not produce the same output for MSVC. GCC and Clang yield just "Foo" if I call `type_name()`, whereas MSVC yield "class Foo". – Joakim Thorén Jul 08 '20 at 12:33
  • 1
    String prefix/suffix and magic numbers are prone to changing. Function https://stackoverflow.com/a/58331141/7163942 calculates them automatically. – Val Sep 03 '20 at 11:36
  • 1
    @Val: Please post your solution as a separate answer here. – einpoklum Oct 22 '20 at 07:36
131

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: 'foo' is not a member of 'std::tuple<int, double, const char*>'
Nurbol Alpysbayev
  • 19,522
  • 3
  • 54
  • 89
NickV
  • 1,576
  • 1
  • 10
  • 8
  • 15
    only c++ could make this so difficult (printing a auto variables type at compile time). ONLY C++. – KP99 Jan 18 '17 at 20:04
  • 3
    @KarlP well to be fair it's a little convoluted, this works too :) ```auto testVar = std::make_tuple(1, 1.0, "abc"); decltype(testVar)::foo = 1;``` – NickV Mar 01 '17 at 13:59
  • On VC++17, this reduces an rvalue-reference to a plain reference, even in a template function with forwarding-reference parameter, and the object name wrapped in std::forward. – Jive Dadson Nov 04 '17 at 17:45
  • You were able to get to the type without creating any new wheels! – Steven Eckhoff Aug 29 '18 at 15:08
  • 5
    This technique is also described in "Item 4: Know how to view deduced types" in Effective Modern C++ – lenkite Jun 07 '19 at 16:57
  • In https://stackoverflow.com/a/74446535 I describe a similar mechanism that works with e.g. clang >=8.0. `static_assert(std::is_same::value, "");`, the compiler telling you that the requirement isn't met and listing the actual type. – Roland Sarrazin Nov 15 '22 at 13:39
63

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;
}
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
mdec
  • 5,122
  • 4
  • 25
  • 26
27

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

paercebal
  • 81,378
  • 38
  • 130
  • 159
25

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;

}

ipapadop
  • 1,432
  • 14
  • 19
24

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".

Nick
  • 27,566
  • 12
  • 60
  • 72
  • 5
    Won't it print "int" for shorts and chars? And "float" for doubles? – gartenriese Feb 17 '14 at 08:00
  • 2
    @gartenriese Specialization does not have that drawback. For `double` it would compile the non-specialized version of the template function rather than do an implicit type conversion to use the specialization: http://cpp.sh/2wzc – chappjc Mar 04 '15 at 18:25
  • 2
    @chappjc: I honestly don't know why I asked that back then, it's pretty clear to me now. But thanks for answering a year old question anyways! – gartenriese Mar 05 '15 at 08:25
  • 4
    @gartenriese I figured as much, but "the internet" might have the same question at some point. – chappjc Mar 05 '15 at 08:28
19

Howard Hinnant used magic numbers to extract type name. 康桓瑋 suggested string prefix and suffix. But prefix/suffix keep changing. With “probe_type” type_name automatically calculates prefix and suffix sizes for “probe_type” to extract type name:

#include <string_view>
using namespace std;

namespace typeName {
 template <typename T>
  constexpr string_view wrapped_type_name () {
#ifdef __clang__
    return __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
    return  __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
    return  __FUNCSIG__;
#endif
  }

  class probe_type;
  constexpr string_view probe_type_name ("typeName::probe_type");
  constexpr string_view probe_type_name_elaborated ("class typeName::probe_type");
  constexpr string_view probe_type_name_used (wrapped_type_name<probe_type> ().find (probe_type_name_elaborated) != -1 ? probe_type_name_elaborated : probe_type_name);

  constexpr size_t prefix_size () {
    return wrapped_type_name<probe_type> ().find (probe_type_name_used);
  }

  constexpr size_t suffix_size () {
    return wrapped_type_name<probe_type> ().length () - prefix_size () - probe_type_name_used.length ();
  }

  template <typename T>
  string_view type_name () {
    constexpr auto type_name = wrapped_type_name<T> ();

    return type_name.substr (prefix_size (), type_name.length () - prefix_size () - suffix_size ());
  }
}

#include <iostream>

using typeName::type_name;
using typeName::probe_type;

class test;

int main () {
  cout << type_name<class test> () << endl;

  cout << type_name<const int*&> () << endl;
  cout << type_name<unsigned int> () << endl;

  const int ic = 42;
  const int* pic = &ic;
  const int*& rpic = pic;
  cout << type_name<decltype(ic)> () << endl;
  cout << type_name<decltype(pic)> () << endl;
  cout << type_name<decltype(rpic)> () << endl;

  cout << type_name<probe_type> () << endl;
}

Output

gcc 10.2:

test
const int *&
unsigned int
const int
const int *
const int *&
typeName::probe_type

clang 11.0.0:

test
const int *&
unsigned int
const int
const int *
const int *&
typeName::probe_type

VS 2019 version 16.7.6:

class test
const int*&
unsigned int
const int
const int*
const int*&
class typeName::probe_type
Val
  • 211
  • 2
  • 4
  • 1
    Great solution! The class_specifier however is lacking one trailing space, thus the prefix size is miscalculated by being one character smaller than it should be. Can't edit the post myself, as it's too small a change to make. The compiler outputs should then be changed accordingly. – Fabio A. Sep 02 '20 at 09:46
  • 1
    Actually, you're making an assumption nor qualitatively different than @康桓瑋 : The assumption that for msvc there's an extra prefix of "class ", and for GCC and clang it doesn't exist. If prefixes and suffixes can change, so can _that_. So it's not clear this is better then just making an assumption about the entire prefix. – einpoklum Oct 22 '20 at 20:36
  • ... anyway, would you mind looking at my own suggested solution, below? And tell me whether I need some extra adjustment for MSVC? – einpoklum Oct 22 '20 at 21:37
  • @einpoklum: Going from magic numbers to magic strings to calculated strings using predefined type (with compiler assumptions) makes less brittle code. To move along this route notice that compilers use type name (``) or elaborated type specifier (`class `) and instead of assuming which compiler does what let calculate wrapped text and determine specific usage (modified code above) and go with it. For 2nd question: The code runs good with MSVC Visual Studio 2019 Version 16.7.6. (Fails on directed websites for MSVC). – Val Oct 26 '20 at 16:10
  • @康桓瑋: Using `void` (or `int`) for probe_type works good.But it assumes prefix not containing these names.It would seem less brittle with more context customized by class/ namespace names. – Val Oct 26 '20 at 16:24
19

Another take on @康桓瑋's answer (originally ), making less assumptions about the prefix and suffix specifics, and inspired by @Val's answer - but without polluting the global namespace; without any conditions; and hopefully easier to read.

The popular compilers provide a macro with the current function's signature. Now, functions are templatable; so the signature contains the template arguments. So, the basic approach is: Given a type, be in a function with that type as a template argument.

Unfortunately, the type name is wrapped in text describing the function, which is different between compilers. For example, with GCC, the signature of template <typename T> int foo() with type double is: int foo() [T = double].

So, how do you get rid of the wrapper text? @HowardHinnant's solution is the shortest and most "direct": Just use per-compiler magic numbers to remove a prefix and a suffix. But obviously, that's very brittle; and nobody likes magic numbers in their code. It turns out, however, that given the macro value for a type with a known name, you can determine what prefix and suffix constitute the wrapping.

#include <string_view>

template <typename T> constexpr std::string_view type_name();

template <>
constexpr std::string_view type_name<void>()
{ return "void"; }

namespace detail {

using type_name_prober = void;

template <typename T>
constexpr std::string_view wrapped_type_name() 
{
#ifdef __clang__
    return __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
    return __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
    return __FUNCSIG__;
#else
#error "Unsupported compiler"
#endif
}

constexpr std::size_t wrapped_type_name_prefix_length() { 
    return wrapped_type_name<type_name_prober>().find(type_name<type_name_prober>()); 
}

constexpr std::size_t wrapped_type_name_suffix_length() { 
    return wrapped_type_name<type_name_prober>().length() 
        - wrapped_type_name_prefix_length() 
        - type_name<type_name_prober>().length();
}

} // namespace detail

template <typename T>
constexpr std::string_view type_name() {
    constexpr auto wrapped_name = detail::wrapped_type_name<T>();
    constexpr auto prefix_length = detail::wrapped_type_name_prefix_length();
    constexpr auto suffix_length = detail::wrapped_type_name_suffix_length();
    constexpr auto type_name_length = wrapped_name.length() - prefix_length - suffix_length;
    return wrapped_name.substr(prefix_length, type_name_length);
}

See it on GodBolt. This should be working with MSVC as well.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • Great decision to isolate code with macros and prefix/suffix operations. – Val Oct 26 '20 at 16:31
  • 1
    @Val: I was essentially just trying to follow the "principle of least astonishment". – einpoklum Oct 26 '20 at 19:44
  • Why is necessary the template specialization? – Alex Vergara Dec 27 '22 at 17:37
  • 1
    @AlexVergara: We use that one to be able to tell which part of the "pretty function signature" is the actual type, and which is the prefix and suffix. We just need such signature for which we know the type name, to identify it without the larger signature. `void` is an almost-arbitrary choice. – einpoklum Dec 27 '22 at 17:45
13

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;
}
Jonas
  • 6,915
  • 8
  • 35
  • 53
abodeofcode
  • 131
  • 1
  • 2
  • 1
    would it be simpler be use a helper function: `template void print_type(T){cout << "type T is: "<< type_id_with_cvr().pretty_name()<< '\n';}` – r0n9 Oct 18 '17 at 01:47
13

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".

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
11

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.

Alan
  • 1,479
  • 3
  • 20
  • 36
8

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;
}
James Hopkin
  • 13,797
  • 1
  • 42
  • 71
7

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

Calls to std::type_info::name are not guaranteed to return anything 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 and y's type is const int*

Alex Vergara
  • 1,766
  • 1
  • 10
  • 29
Milo Lu
  • 3,176
  • 3
  • 35
  • 46
6

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);};
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
5

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*"; }
Jahid
  • 21,542
  • 10
  • 90
  • 108
  • 2
    (i) it won't work for other types (i.e. not generic at all); (ii) useless code bloat; (iii) the same can be (correctly) done with `typeid` or `decltype`. – edmz Mar 13 '15 at 19:57
  • 2
    You are right, but it covers all the basic types...and that's what I need right now.. – Jahid Mar 13 '15 at 20:08
  • 2
    Can you tell me, how would you do it with decltype, – Jahid Mar 13 '15 at 20:16
  • 1
    If it's a compile-time test, you can use std::is_same and decltype to get T and S. – edmz Mar 14 '15 at 07:11
5

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
Jahid
  • 21,542
  • 10
  • 90
  • 108
3
#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
Graywolf
  • 299
  • 2
  • 6
3

For anyone still visiting, I've recently had the same issue and decided to write a small library based on answers from this post. It provides constexpr type names and type indices und is is tested on Mac, Windows and Ubuntu.

The library code is here: https://github.com/TheLartians/StaticTypeInfo

Lars Melchior
  • 292
  • 1
  • 9
2

For something different, here's a "To English" conversion of the type, deconstructing every qualifier, extent, argument, and so on, recursively building the string describing the type I think the "deduced this" proposal would help cut down many of the specializations. In any case, this was a fun morning exercise, regardless of excessive bloat. :)

struct X {
    using T = int *((*)[10]);
    T f(T, const unsigned long long * volatile * );
};

int main() {

    std::cout << describe<decltype(&X::f)>() << std::endl;
}

Output:

pointer to member function of class 1X taking (pointer to array[10]
of pointer to int, pointer to volatile pointer to const unsigned 
long long), and returning pointer to array[10] of pointer to int

Here's the code: https://godbolt.org/z/7jKK4or43

Note: most current version is in my github: https://github.com/cuzdav/type_to_string

// Print types as strings, including functions, member 

#include <type_traits>
#include <typeinfo>
#include <string>
#include <utility>

namespace detail {

template <typename T> struct Describe;

template <typename T, class ClassT> 
struct Describe<T (ClassT::*)>  {
    static std::string describe();
};
template <typename RetT, typename... ArgsT> 
struct Describe<RetT(ArgsT...)>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...)>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...)&>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const &>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile &>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) & noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile &>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const & noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile & noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile & noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) &&>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const &&>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile &&>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) && noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile &&>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const && noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile && noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile && noexcept>  {
    static std::string describe();
};

template <typename T>
std::string describe()
{
    using namespace std::string_literals;
    auto terminal = [&](char const * desc) {
        return desc + " "s + typeid(T).name();
    };
    if constexpr(std::is_const_v<T>) {
        return "const " + describe<std::remove_const_t<T>>();
    }
    else if constexpr(std::is_volatile_v<T>) {
        return "volatile " + describe<std::remove_volatile_t<T>>();
    }
    else if constexpr (std::is_same_v<bool, T>) {
        return "bool";
    }
    else if constexpr(std::is_same_v<char, T>) {
        return "char";
    }
    else if constexpr(std::is_same_v<signed char, T>) {
        return "signed char";
    }
    else if constexpr(std::is_same_v<unsigned char, T>) {
        return "unsigned char";
    }
    else if constexpr(std::is_unsigned_v<T>) {
        return "unsigned " + describe<std::make_signed_t<T>>();
    }
    else if constexpr(std::is_void_v<T>) {
        return "void";
    }
    else if constexpr(std::is_integral_v<T>) {
        if constexpr(std::is_same_v<short, T>) 
            return "short";
        else if constexpr(std::is_same_v<int, T>) 
            return "int";
        else if constexpr(std::is_same_v<long, T>) 
            return "long";
        else if constexpr(std::is_same_v<long long, T>) 
            return "long long";
    }
    else if constexpr(std::is_same_v<float, T>) {
        return "float";
    }
    else if constexpr(std::is_same_v<double, T>) {
        return "double";
    }
    else if constexpr(std::is_same_v<long double, T>) {
        return "long double";
    }
    else if constexpr(std::is_same_v<std::nullptr_t, T>) { 
        return "nullptr_t";
    }
    else if constexpr(std::is_class_v<T>) {
        return terminal("class");
    }
    else if constexpr(std::is_union_v<T>) {
        return terminal("union");
    }
    else if constexpr(std::is_enum_v<T>) {
        std::string result;
        if (!std::is_convertible_v<T, std::underlying_type_t<T>>) {
            result += "scoped ";
        }
        return result + terminal("enum");
    }  
    else if constexpr(std::is_pointer_v<T>) {
        return "pointer to " + describe<std::remove_pointer_t<T>>();
    }
    else if constexpr(std::is_lvalue_reference_v<T>) {
        return "lvalue-ref to " + describe<std::remove_reference_t<T>>();
    }
    else if constexpr(std::is_rvalue_reference_v<T>) {
        return "rvalue-ref to " + describe<std::remove_reference_t<T>>();
    }
    else if constexpr(std::is_bounded_array_v<T>) {
        return "array[" + std::to_string(std::extent_v<T>) + "] of " +
            describe<std::remove_extent_t<T>>();
    }
    else if constexpr(std::is_unbounded_array_v<T>) {
        return "array[] of " + describe<std::remove_extent_t<T>>();
    }
    else if constexpr(std::is_function_v<T>) {
        return Describe<T>::describe();
    }
    else if constexpr(std::is_member_object_pointer_v<T>) {
        return Describe<T>::describe();
    }
    else if constexpr(std::is_member_function_pointer_v<T>) {
        return Describe<T>::describe();
    }
}

template <typename RetT, typename... ArgsT> 
std::string Describe<RetT(ArgsT...)>::describe() {
    std::string result = "function taking (";
    ((result += detail::describe<ArgsT>(", ")), ...);
    return result + "), returning " + detail::describe<RetT>();
}

template <typename T, class ClassT> 
std::string Describe<T (ClassT::*)>::describe() {
    return "pointer to member of " + detail::describe<ClassT>() +
        " of type " + detail::describe<T>();
}

struct Comma {
    char const * sep = "";
    std::string operator()(std::string const& str) {
        return std::exchange(sep, ", ") + str;
    }
};
enum Qualifiers {NONE=0, CONST=1, VOLATILE=2, NOEXCEPT=4, LVREF=8, RVREF=16};

template <typename RetT, typename ClassT, typename... ArgsT>
std::string describeMemberPointer(Qualifiers q) {
    std::string result = "pointer to ";
    if (NONE != (q & CONST)) result += "const ";
    if (NONE != (q & VOLATILE)) result += "volatile ";
    if (NONE != (q & NOEXCEPT)) result += "noexcept ";
    if (NONE != (q & LVREF)) result += "lvalue-ref ";
    if (NONE != (q & RVREF)) result += "rvalue-ref ";
    result += "member function of " + detail::describe<ClassT>() + " taking (";
    Comma comma;
    ((result += comma(detail::describe<ArgsT>())), ...);
    return result + "), and returning " + detail::describe<RetT>();
}

template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...)>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(NONE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(CONST);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(VOLATILE | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(CONST | VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(CONST | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(CONST | VOLATILE | NOEXCEPT);
}

template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) &>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const &>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | CONST);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) & noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile &>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile & noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | VOLATILE | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile &>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | CONST | VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const & noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | CONST | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile & noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | CONST | VOLATILE | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...)&&>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const &&>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | CONST);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) && noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile &&>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile && noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | VOLATILE | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile &&>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | CONST | VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const && noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | CONST | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile && noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | CONST | VOLATILE | NOEXCEPT);
}

} // detail

///////////////////////////////////
// Main function
///////////////////////////////////
template <typename T>
std::string describe() {
    return detail::describe<T>();
}


///////////////////////////////////
// Sample code
///////////////////////////////////
#include <iostream>


struct X {
    using T = int *((*)[10]);
    T f(T, const unsigned long long * volatile * );
};

int main() {
    std::cout << describe<decltype(&X::f)>() << std::endl;
}

Chris Uzdavinis
  • 6,022
  • 9
  • 16
1

Copying from this answer: https://stackoverflow.com/a/56766138/11502722

I was able to get this somewhat working for C++ static_assert(). The wrinkle here is that static_assert() only accepts string literals; constexpr string_view will not work. You will need to accept extra text around the typename, but it works:

template<typename T>
constexpr void assertIfTestFailed()
{
#ifdef __clang__
    static_assert(testFn<T>(), "Test failed on this used type: " __PRETTY_FUNCTION__);
#elif defined(__GNUC__)
    static_assert(testFn<T>(), "Test failed on this used type: " __PRETTY_FUNCTION__);
#elif defined(_MSC_VER)
    static_assert(testFn<T>(), "Test failed on this used type: " __FUNCSIG__);
#else
    static_assert(testFn<T>(), "Test failed on this used type (see surrounding logged error for details).");
#endif
    }
}

MSVC Output:

error C2338: Test failed on this used type: void __cdecl assertIfTestFailed<class BadType>(void)
... continued trace of where the erroring code came from ...
1

Building on a number of the previous answers, I made this solution which does not store the result of __PRETTY_FUNCTION__ in the binary. It uses a static array to hold the string representation of the type name.

It requires C++23.

#include <iostream>
#include <string_view>
#include <array>

template <typename T>
constexpr auto type_name() {
    auto gen = [] <class R> () constexpr -> std::string_view  {
        return __PRETTY_FUNCTION__;
    };
    constexpr std::string_view search_type = "float";
    constexpr auto search_type_string = gen.template operator()<float>();
    constexpr auto prefix = search_type_string.find(search_type);
    constexpr auto suffix = search_type_string.size() - prefix - search_type.size();
    constexpr auto str = gen.template operator()<T>();
    constexpr int size = str.size() - prefix - suffix;
    constexpr auto static arr = [&]<std::size_t... I>(std::index_sequence<I...>) constexpr {
        return std::array<char, size>{str[prefix + I]...};
    } (std::make_index_sequence<size>{});

    return std::string_view(arr.data(), size);
}
Jonas
  • 6,915
  • 8
  • 35
  • 53
1

C++ Data type resolve in Compile-Time using Template and Runtime using TypeId.

Compile time solution.

template <std::size_t...Idxs>
constexpr auto substring_as_array(std::string_view str, std::index_sequence<Idxs...>)
{
  return std::array{str[Idxs]..., '\n'};
}

template <typename T>
constexpr auto type_name_array()
{
#if defined(__clang__)
  constexpr auto prefix   = std::string_view{"[T = "};
  constexpr auto suffix   = std::string_view{"]"};
  constexpr auto function = std::string_view{__PRETTY_FUNCTION__};
#elif defined(__GNUC__)
  constexpr auto prefix   = std::string_view{"with T = "};
  constexpr auto suffix   = std::string_view{"]"};
  constexpr auto function = std::string_view{__PRETTY_FUNCTION__};
#elif defined(_MSC_VER)
  constexpr auto prefix   = std::string_view{"type_name_array<"};
  constexpr auto suffix   = std::string_view{">(void)"};
  constexpr auto function = std::string_view{__FUNCSIG__};
#else
# error Unsupported compiler
#endif

  constexpr auto start = function.find(prefix) + prefix.size();
  constexpr auto end = function.rfind(suffix);

  static_assert(start < end);

  constexpr auto name = function.substr(start, (end - start));
  return substring_as_array(name, std::make_index_sequence<name.size()>{});
}

template <typename T>
struct type_name_holder {
  static inline constexpr auto value = type_name_array<T>();
};

template <typename T>
constexpr auto type_name() -> std::string_view
{
  constexpr auto& value = type_name_holder<T>::value;
  return std::string_view{value.data(), value.size()};
}

Runtime solution.

template <typename T>
void PrintDataType(T type)
{
    auto name = typeid(type).name();
    string cmd_str = "echo '" + string(name) + "' | c++filt -t";
    system(cmd_str.c_str());
}

Main Code

#include <iostream>
#include <map>
#include <string>
#include <typeinfo>
#include <string_view>
#include <array>   // std::array
#include <utility> // std::index_sequence
using std::string;
int main()
{
    //Dynamic resolution.
    std::map<int, int> iMap;
    PrintDataType(iMap);
    
    //Compile type resolution.
    std::cout << type_name<std::list<int>>() << std::endl;

    return 0;
}

Code Snippet

Haseeb Mir
  • 928
  • 1
  • 13
  • 22
  • The rules require answers to be self-contained. Can you add the the code from the link to the answer itself? – HolyBlackCat Jun 16 '22 at 18:28
  • @HolyBlackCat okay now i have added Main method showing code running. – Haseeb Mir Jun 16 '22 at 18:34
  • That's not what I meant. I meant adding all code from the link. – HolyBlackCat Jun 16 '22 at 18:34
  • @HolyBlackCat You mean the whole code snippet itself ? but why the code is too big to read itself so i posted some chunks of code to show relevant information. – Haseeb Mir Jun 16 '22 at 18:38
  • 1
    80 lines isn't much. The point is, your answer should still be valid even when/if the link dies. Here's more information: [Are answers that just contain links elsewhere really "good answers"?](https://meta.stackexchange.com/q/8231/353058). – HolyBlackCat Jun 16 '22 at 18:41
  • 1
    okay I will post answer here as well thanks. – Haseeb Mir Jun 16 '22 at 18:45
-3

Consider this code:

#include <iostream>

int main()
{
    int a = 2; // Declare type "int"
    std::string b = "Hi"; // Declare type "string"
    long double c = 3438; // Declare type "long double"
    if(typeid(a) == typeid(int))
    {
        std::cout<<"int\n";
    }

    if(typeid(b) == typeid(std::string))
    {
        std::cout<<"string\n";
    }
    
    if(typeid(c) == typeid(long double))
    {
        std::cout<<"long double";
    }
    return 0;
}

I believe you want the whole word (rather than only printing the short form of int (which is i), you want int), that is why I did the if.

For some of the variables (string,long double etc... which do not print the expected result comparing their short forms), you need to compare the result of applying the typeid operator with the typeid of a specific type.

From cppreference:

Returns an implementation defined null-terminated character string containing the name of the type. No guarantees are given; in particular, the returned string can be identical for several types and change between invocations of the same program.


In my opinion, Python is better than C++ in this case. Python has built-in type function to directly access the data type of the variable.