118

I'm writing some template classes for parseing some text data files, and as such it is likly the great majority of parse errors will be due to errors in the data file, which are for the most part not written by programmers, and so need a nice message about why the app failed to load e.g. something like:

Error parsing example.txt. Value ("notaninteger")of [MySectiom]Key is not a valid int

I can work out the file, section and key names from the arguments passed to the template function and member vars in the class, however I'm not sure how to get the name of the type the template function is trying to convert to.

My current code looks like, with specialisations for just plain strings and such:

template<typename T> T GetValue(const std::wstring &section, const std::wstring &key)
{
    std::map<std::wstring, std::wstring>::iterator it = map[section].find(key);
    if(it == map[section].end())
        throw ItemDoesNotExist(file, section, key)
    else
    {
        try{return boost::lexical_cast<T>(it->second);}
        //needs to get the name from T somehow
        catch(...)throw ParseError(file, section, key, it->second, TypeName(T));
    }
}

Id rather not have to make specific overloads for every type that the data files might use, since there are loads of them...

Also I need a solution that does not incur any runtime overhead unless an exception occurs, i.e. a completely compile time solution is what I want since this code is called tons of times and load times are already getting somewhat long.

EDIT: Ok this is the solution I came up with:

I have a types.h containg the following

#pragma once
template<typename T> const wchar_t *GetTypeName();

#define DEFINE_TYPE_NAME(type, name) \
    template<>const wchar_t *GetTypeName<type>(){return name;}

Then I can use the DEFINE_TYPE_NAME macro to in cpp files for each type I need to deal with (eg in the cpp file that defined the type to start with).

The linker is then able to find the appropirate template specialisation as long as it was defined somewhere, or throw a linker error otherwise so that I can add the type.

1
  • 1
    not really relevant to your question, but you might want to use map.find(section) when accessing the section aswell, unless you intentionally want to create an empty section.
    – Idan K
    Commented Jun 28, 2009 at 18:45

14 Answers 14

118

The solution is:

typeid(T).name()

where typeid(T) returns std::type_info.

5
  • 8
    Keep in mind that it's compliant to return the same string for every type (though I don't think any compiler would do that).
    – Motti
    Commented Jun 28, 2009 at 19:03
  • 3
    Or to return a different string for the same type on different executions... (again not that I think that any sane compiler would do that).
    – Emily L.
    Commented Nov 27, 2014 at 19:03
  • 9
    I'd just like to point out how ugly the given name can be: typeid(simd::double3x4).name() = "N4simd9double3x4E". typeid(simd::float4).name() = "Dv4_f" C++17, Xcode 10.1. Commented Dec 26, 2018 at 1:50
  • 2
    Indeed. typeid(T).name() is the canonical way to do this, but very few compilers return unmangled names; the only one I'm personally familiar with that does so is MSVC. Depending on the compiler used, there's also a chance that it may lose some type information on function types, but that's probably irrelevant in this case. Commented Sep 24, 2019 at 16:40
  • 3
    typeid(T).name() does not return std::type_info, but char const *. Commented Dec 7, 2020 at 16:01
68

typeid(T).name() is implementation defined and doesn't guarantee human readable string.

Reading cppreference.com :

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.

...

With compilers such as gcc and clang, the returned string can be piped through c++filt -t to be converted to human-readable form.

But in some cases gcc doesn't return right string. For example on my machine I have gcc whith -std=c++11 and inside template function typeid(T).name() returns "j" for "unsigned int". It's so called mangled name. To get real type name, use abi::__cxa_demangle() function (gcc only):

#include <string>
#include <cstdlib>
#include <cxxabi.h>

template<typename T>
std::string type_name()
{
    int status;
    std::string tname = typeid(T).name();
    char *demangled_name = abi::__cxa_demangle(tname.c_str(), NULL, NULL, &status);
    if(status == 0) {
        tname = demangled_name;
        std::free(demangled_name);
    }   
    return tname;
}
4
  • 1
    Isn't it memory leak to have free in if? Commented Aug 17, 2016 at 3:48
  • 2
    No, because the pointer points to nullptr if the status is not 0. Commented Feb 3, 2017 at 19:05
  • 3
    I'd like to add that it's probably best to check for gcc or clang's existence and if not default to not doing the demangling as shown here. Commented Jul 10, 2017 at 20:30
  • 1
    This solution does not display type qualifier (e.g. const) there is a similar approach that display both type and qualifier here
    – arkan
    Commented Sep 23, 2022 at 16:08
48

Jesse Beder's solution is likely the best, but if you don't like the names typeid gives you (I think gcc gives you mangled names for instance), you can do something like:

template<typename T>
struct TypeParseTraits;

#define REGISTER_PARSE_TYPE(X) template <> struct TypeParseTraits<X> \
    { static const char* name; } ; const char* TypeParseTraits<X>::name = #X


REGISTER_PARSE_TYPE(int);
REGISTER_PARSE_TYPE(double);
REGISTER_PARSE_TYPE(FooClass);
// etc...

And then use it like

throw ParseError(TypeParseTraits<T>::name);

EDIT:

You could also combine the two, change name to be a function that by default calls typeid(T).name() and then only specialize for those cases where that's not acceptable.

5
  • Note: This code will not compile if you forget to define REGISTER_PARSE_TYPE for a type that you use. I have used a similar trick before (in code without RTTI) and it has worked very well.
    – Tom Leys
    Commented Jun 28, 2009 at 21:20
  • 1
    I had to move name outside of the struct in g++ 4.3.0 due to "error: invalid in-class initialization of static data member of non-integral type 'const char *'"; and, of course, the keyword 'struct' is needed between <> and TypeParseTraits and the definition should be terminated with a semicolon.
    – fuzzyTew
    Commented Jul 17, 2009 at 10:35
  • 4
    Well the leaving the semicolon out was intentional, to force you to use it at the end of the macro invocation, but thanks for the corrections. Commented Jul 17, 2009 at 11:44
  • I obtain the folllowing error: error: '#' is not followed by a macro parameter
    – kratsg
    Commented Feb 26, 2015 at 3:51
  • @kratsg - that's because at the end '#x' should be '#X' (uppercase to match macro parameter) - I'll fix the answer.
    – amdn
    Commented Mar 25, 2015 at 1:25
31

This trick was mentioned under a few other questions, but not here yet.

All major compilers support __PRETTY_FUNC__ (GCC & Clang) /__FUNCSIG__ (MSVC) as an extension.

When used in a template like this:

template <typename T> const char *foo()
{
    #ifdef _MSC_VER
    return __FUNCSIG__;
    #else
    return __PRETTY_FUNCTION__;
    #endif
}

It produces strings in a compiler-dependent format, that contain, among other things, the name of T.

E.g. foo<float>() returns:

  • "const char* foo() [with T = float]" on GCC
  • "const char *foo() [T = float]" on Clang
  • "const char *__cdecl foo<float>(void)" on MSVC

You can easily parse the type names out of those strings. You just need to figure out how many 'junk' characters your compiler inserts before and after the type.

You can even do that completely at compile-time.


The resulting names can slightly vary between different compilers. E.g. GCC omits default template arguments, and MSVC prefixes classes with the word class.


Here's an implementation that I've been using. Everything is done at compile-time.

Example usage:

std::cout << TypeName<float>() << '\n';
std::cout << TypeName<decltype(1.2f)>(); << '\n';

Implementation: (uses C++20, but can be backported; see the edit history for a C++17 version)

#include <algorithm>
#include <array>
#include <cstddef>
#include <string_view>

namespace impl
{
    template <typename T>
    [[nodiscard]] constexpr std::string_view RawTypeName()
    {
        #ifndef _MSC_VER
        return __PRETTY_FUNCTION__;
        #else
        return __FUNCSIG__;
        #endif
    }

    struct TypeNameFormat
    {
        std::size_t junk_leading = 0;
        std::size_t junk_total = 0;
    };

    constexpr TypeNameFormat type_name_format = []{
        TypeNameFormat ret;
        std::string_view sample = RawTypeName<int>();
        ret.junk_leading = sample.find("int");
        ret.junk_total = sample.size() - 3;
        return ret;
    }();
    static_assert(type_name_format.junk_leading != std::size_t(-1), "Unable to determine the type name format on this compiler.");

    template <typename T>
    static constexpr auto type_name_storage = []{
        std::array<char, RawTypeName<T>().size() - type_name_format.junk_total + 1> ret{};
        std::copy_n(RawTypeName<T>().data() + type_name_format.junk_leading, ret.size() - 1, ret.data());
        return ret;
    }();
}

template <typename T>
[[nodiscard]] constexpr std::string_view TypeName()
{
    return {impl::type_name_storage<T>.data(), impl::type_name_storage<T>.size() - 1};
}

template <typename T>
[[nodiscard]] constexpr const char *TypeNameCstr()
{
    return impl::type_name_storage<T>.data();
}
3
  • THAT is the real answer!! Absolutly beatifull, no need std lib, and it runs complie time. In embedded code, this is the only solution. Thank you!! Commented Feb 20, 2022 at 17:24
  • I like this answer as well, but beware GCC < 8.0 does not define __PRETTY_FUNCTION__ as a constexpr - which is on the default gcc on Ubuntu 18. Also, here is a similar-in-concept implementation (also based on __PRETTY_FUNCTION__).
    – eclarkso
    Commented May 23, 2022 at 21:21
  • This worked for me. I directly printed the string output of __PRETTY_FUNCTION__. My codebase didn't support typeid.
    – shivank
    Commented Feb 27, 2023 at 6:24
27

As mentioned by Bunkar typeid(T).name is implementation defined.

To avoid this issue you can use Boost.TypeIndex library.

For example:

boost::typeindex::type_id<T>().pretty_name() // human readable
3
  • This is very useful for finding out templates type names when functions are called. It worked pretty well for me.
    – Fernando
    Commented Jun 28, 2017 at 22:09
  • 1
    Note that pretty_name() or raw_name() is still implementation defined. On MSVC for a struct A; you would get : "struct A" while on gcc/clang : "A".
    – daminetreg
    Commented Jan 10, 2018 at 10:58
  • wow. boost again for the win. amazing what boost does without compiler support (auto, regex, foreach, threads, static_assert, etc, etc... support before compilers/C++-standard support). Commented Apr 12, 2018 at 16:29
15

The answer of Logan Capaldo is correct but can be marginally simplified because it is unnecessary to specialize the class every time. One can write:

// in header
template<typename T>
struct TypeParseTraits
{ static const char* name; };

// in c-file
#define REGISTER_PARSE_TYPE(X) \
    template <> const char* TypeParseTraits<X>::name = #X

REGISTER_PARSE_TYPE(int);
REGISTER_PARSE_TYPE(double);
REGISTER_PARSE_TYPE(FooClass);
// etc...

This also allows you to put the REGISTER_PARSE_TYPE instructions in a C++ file...

8

As a rephrasing of Andrey's answer:

The Boost TypeIndex library can be used to print names of types.

Inside a template, this might read as follows

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

template<typename T>
void printNameOfType() {
    std::cout << "Type of T: " 
              << boost::typeindex::type_id<T>().pretty_name() 
              << std::endl;
}
3

If you'd like a pretty_name, Logan Capaldo's solution can't deal with complex data structure: REGISTER_PARSE_TYPE(map<int,int>) and typeid(map<int,int>).name() gives me a result of St3mapIiiSt4lessIiESaISt4pairIKiiEEE

There is another interesting answer using unordered_map or map comes from https://en.cppreference.com/w/cpp/types/type_index.

#include <iostream>
#include <unordered_map>
#include <map>
#include <typeindex>
using namespace std;
unordered_map<type_index,string> types_map_;

int main(){
    types_map_[typeid(int)]="int";
    types_map_[typeid(float)]="float";
    types_map_[typeid(map<int,int>)]="map<int,int>";

    map<int,int> mp;
    cout<<types_map_[typeid(map<int,int>)]<<endl;
    cout<<types_map_[typeid(mp)]<<endl;
    return 0;
}
2

typeid(uint8_t).name() is nice, but it returns "unsigned char" while you may expect "uint8_t".

This piece of code will return you the appropriate type

#define DECLARE_SET_FORMAT_FOR(type) \
    if ( typeid(type) == typeid(T) ) \
        formatStr = #type;

template<typename T>
static std::string GetFormatName()
{
    std::string formatStr;

    DECLARE_SET_FORMAT_FOR( uint8_t ) 
    DECLARE_SET_FORMAT_FOR( int8_t ) 

    DECLARE_SET_FORMAT_FOR( uint16_t )
    DECLARE_SET_FORMAT_FOR( int16_t )

    DECLARE_SET_FORMAT_FOR( uint32_t )
    DECLARE_SET_FORMAT_FOR( int32_t )

    DECLARE_SET_FORMAT_FOR( float )

    // .. to be exptended with other standard types you want to be displayed smartly

    if ( formatStr.empty() )
    {
        assert( false );
        formatStr = typeid(T).name();
    }

    return formatStr;
}
2
  • This is great, but why not do return #type; instead? Commented Sep 20, 2020 at 19:59
  • @LittleHelper: You are right, that would also work...
    – jpo38
    Commented Sep 21, 2020 at 8:57
1

Complementing the awesome answer by @HolyBlackCat which does the job at compile time. I've managed to encapsulate the logic inside a struct so there's no need for a "impl" namespace, instead the 'impl' functions are guarded in a private manner.

template<typename T>
struct TypeInfo
{
    private:
        struct RawTypeNameFormat
        {
            std::size_t leading_junk = 0;
            std::size_t trailing_junk = 0;
        };

        template<typename U>
        static constexpr const auto& RawTypeName()
        {
#ifdef _MSC_VER
            return __FUNCSIG__;
#else
            return __PRETTY_FUNCTION__;
#endif
        }

        // Returns `false` on failure.
        static constexpr inline bool GetRawTypeNameFormat(RawTypeNameFormat *format)
        {
            const auto &str = RawTypeName<int>();
            for (std::size_t i = 0;; i++)
            {
                if (str[i] == 'i' && str[i+1] == 'n' && str[i+2] == 't')
                {
                    if (format)
                    {
                        format->leading_junk = i;
                        format->trailing_junk = sizeof(str)-i-3-1; // `3` is the length of "int", `1` is the space for the null terminator.
                    }
                    return true;
                }
            }
            return false;
        }

        static constexpr inline RawTypeNameFormat format =
        []{
            static_assert(GetRawTypeNameFormat(nullptr), "Unable to figure out how to generate type names on this compiler.");
            RawTypeNameFormat format;
            GetRawTypeNameFormat(&format);
            return format;
        }();


        // Returns the type name in a `std::array<char, N>` (null-terminated).
        [[nodiscard]]
        static constexpr auto GetTypeName()
        {
            constexpr std::size_t len = sizeof(RawTypeName<T>()) - format.leading_junk - format.trailing_junk;
            std::array<char, len> name{};
            for (std::size_t i = 0; i < len - 1; i++)
                name[i] = RawTypeName<T>()[i + format.leading_junk];
            return name;
        }

    public:
        [[nodiscard]]
        static cstring Name()
        {
            static constexpr auto name = GetTypeName();
            return name.data();
        }

        [[nodiscard]]
        static cstring Name(const T&)
        {
            return name();
        }
}

Example usage:

auto myTypeName = TypeInfo<MyType>::Name();

or:

const char* myTypeName = TypeInfo<MyType>::Name();
1

Since c++20 we can use std::source_location::function_name() to get a string containing the function name and arguments.

template<typename T>
consteval auto type_name()
{
    std::string_view func_name(std::source_location::current().function_name()); // returns something like: consteval auto type_name() [with T = int]

    auto extracted_params = ... Do some post processing here to extract the parameter names.
    return extracted_params;
}

N.B.: at the time of writing (Oct 2022) MSVC does not report the template parameters, so this solution will not work there. Unfortunately, the form of the return value of function_name() isn't specified in the standard, but we may at least hope that they will add the template parameters in later versions.

Example

0

There're many good answers here, but I'd guess the easiest is to use a library, isn't it? You can use this tiny lib, it's pretty cross-platform (tested with recent GCC/Clang/MSVC/ICC), supports C++11 onwards, works at compile time (consteval since C++20) and in theory it's going to support every compiler since the C++20's <source_location> gains wider support. A few more notes are in the README :)

0

I found this trick to publish a service interface with type information

#include <iostream>

using namespace std;
const char* nameOfType(int& ){ return "int";}
const char* nameOfType(const char* &){ return "string";}
const char* nameOfType(float& ){ return "float";}
const char* nameOfType(double& ){ return "double";}

template <typename T> 
const char* templateFunction(T t){
  return nameOfType(t)  ;
};

int main()
{
    cout<<"Hello World this is an ";
    cout << templateFunction<int>(1) << std::endl;
    cout << templateFunction<const char*>("") << std::endl;
    cout << templateFunction<double>(3.14) <<std::endl;
    return 0;
}

Hope it can help somebody.

-1

I just leave it there. If someone will still need it, then you can use this:

template <class T>
bool isString(T* t) { return false;  } // normal case returns false

template <>
bool isString(char* t) { return true; }  // but for char* or String.c_str() returns true
.
.
.

This will only CHECK type not GET it and only for 1 type or 2.

Not the answer you're looking for? Browse other questions tagged or ask your own question.