To fix the second issue, we will parse the type name string at runtime and generate a prettified and consistent one across major compilers.

We will start by checking the template parameter type, if it is a primitive type, we can just return a string literal with the correct name, otherwise we parse it.

inline static constexpr size_t REFLECT_MAX_NAME_LENGTH = 128;

template <typename T>
inline static constexpr const char *
name_of()
{
         if constexpr (std::is_same_v<T, int8_t>)   return "i8";
    else if constexpr (std::is_same_v<T, int16_t>)  return "i16";
    else if constexpr (std::is_same_v<T, int32_t>)  return "i32";
    else if constexpr (std::is_same_v<T, int64_t>)  return "i64";
    else if constexpr (std::is_same_v<T, uint8_t>)  return "u8";
    else if constexpr (std::is_same_v<T, uint16_t>) return "u16";
    else if constexpr (std::is_same_v<T, uint32_t>) return "u32";
    else if constexpr (std::is_same_v<T, uint64_t>) return "u64";
    else if constexpr (std::is_same_v<T, float>)    return "f32";
    else if constexpr (std::is_same_v<T, double>)   return "f64";
    else if constexpr (std::is_same_v<T, bool>)     return "bool";
    else if constexpr (std::is_same_v<T, char>)     return "char";
    else if constexpr (std::is_same_v<T, void>)     return "void";
    else
    {
        constexpr auto _name_of = [](std::string_view type_name) -> const char * {
            // a static buffer to hold the prettified type name, it lasts for the duration of the application life time.
            static char name[REFLECT_MAX_NAME_LENGTH] = {};
            size_t count = 0;
            _name_of_parse_and_append(name, count, type_name);
            return name;
        };

        #if defined(_MSC_VER)
            constexpr auto type_function_name = std::string_view{__FUNCSIG__};
            constexpr auto type_name_prefix_length = type_function_name.find("name_of<") + 8;
            constexpr auto type_name_length = type_function_name.rfind(">") - type_name_prefix_length;
        #elif defined(__GNUC__)
            constexpr auto type_function_name = std::string_view{__PRETTY_FUNCTION__};
            constexpr auto type_name_prefix_length = type_function_name.find("= ") + 2;
            constexpr auto type_name_length = type_function_name.rfind("]") - type_name_prefix_length;
        #else
            #error "[REFLECT]: Unsupported compiler."
        #endif

        // Generates the name the first time then caches it for subsequent calls.
        static const char *name = _name_of(type_function_name.substr(type_name_prefix_length, type_name_length));
        return name;
    }
}

To parse the type name; first we check if the type itself is a template type or not. If it is not a template type we parse and append the result to the buffer, If it is a template type we divide it into two parts, the type and template parameter type, for example if we have type Foo<int64_t> we divide it into Foo and int64_t then parse each part and append the result to the buffer. We do this recursively to handle nested template types.

Example:

// MSVC:
1. struct Foo<struct Foo<__int64>> -> `struct Foo` and `struct Foo<__int64>`.
2. struct Foo                      -> `Foo`                                   // Output: `Foo<`
3. struct Foo<__int64>             -> `struct Foo` and `__int64`.
4. struct Foo                      -> `Foo`                                   // Output: `Foo<Foo<`
5. __int64                         -> `i64`                                   // Output: `Foo<Foo<i64>>`

// GCC:
1. Foo<Foo<long int>>              -> `Foo` and `Foo<long int>`.
2. Foo                             -> `Foo`                                   // Output: `Foo<`
3. Foo<long int>                   -> `Foo` and `long int`.
4. Foo                             -> `Foo`                                   // Output: `Foo<Foo<`
5. long int                        -> `i64`                                   // Output: `Foo<Foo<i64>>`

// Clang:
1. Foo<Foo<long>>                  -> `Foo` and `Foo<long>`.
2. Foo                             -> `Foo`                                   // Output: `Foo<`
3. Foo<long>                       -> `Foo` and `long`.
4. Foo                             -> `Foo`                                   // Output: `Foo<Foo<`
5. long                            -> `i64`                                   // Output: `Foo<Foo<i64>>`

The parsing function does other things to keep name output consistent; it handles the placement of const keyword as well as the pointer * and reference & operators.

inline static constexpr void
_name_of_parse_and_append(char *name, size_t &count, std::string_view type_name)
{
    constexpr auto string_append = [](char *string, const char *to_append, size_t &count) {
        while (*to_append != '\0' && count < REFLECT_MAX_NAME_LENGTH - 1)
            string[count++] = *to_append++;
    };

    constexpr auto append_type_name_prettified = [string_append](char *name, std::string_view type_name, size_t &count) {
        if (type_name.starts_with(' '))
            type_name.remove_prefix(1);

        bool add_pointer = false;
        if (type_name.starts_with("const "))
        {
            string_append(name, "const ", count);
            type_name.remove_prefix(6);
        }
        else if (type_name.ends_with(" const *"))
        {
            string_append(name, "const ", count);
            type_name.remove_suffix(8);
            add_pointer = true;
        }

        #if defined(_MSC_VER)
            if (type_name.starts_with("enum "))
                type_name.remove_prefix(5);
            else if (type_name.starts_with("class "))
                type_name.remove_prefix(6);
            else if (type_name.starts_with("struct "))
                type_name.remove_prefix(7);
        #endif

        if (type_name.starts_with("signed char"))
        {
            string_append(name, "i8", count);
            type_name.remove_prefix(11);
        }
        else if (type_name.starts_with("short int"))
        {
            string_append(name, "i16", count);
            type_name.remove_prefix(9);
        }
        else if (type_name.starts_with("short") && !type_name.starts_with("short unsigned int"))
        {
            string_append(name, "i16", count);
            type_name.remove_prefix(5);
        }
        else if (type_name.starts_with("int"))
        {
            string_append(name, "i32", count);
            type_name.remove_prefix(3);
        }
        else if (type_name.starts_with("__int64"))
        {
            string_append(name, "i64", count);
            type_name.remove_prefix(7);
        }
        else if (type_name.starts_with("long int"))
        {
            string_append(name, "i64", count);
            type_name.remove_prefix(8);
        }
        else if (type_name.starts_with("unsigned char"))
        {
            string_append(name, "u8", count);
            type_name.remove_prefix(13);
        }
        else if (type_name.starts_with("unsigned short"))
        {
            string_append(name, "u16", count);
            type_name.remove_prefix(14);
        }
        else if (type_name.starts_with("short unsigned int"))
        {
            string_append(name, "u16", count);
            type_name.remove_prefix(18);
        }
        else if (type_name.starts_with("unsigned int"))
        {
            string_append(name, "u32", count);
            type_name.remove_prefix(12);
        }
        else if (type_name.starts_with("unsigned __int64"))
        {
            string_append(name, "u64", count);
            type_name.remove_prefix(16);
        }
        else if (type_name.starts_with("long unsigned int"))
        {
            string_append(name, "u64", count);
            type_name.remove_prefix(17);
        }
        else if (type_name.starts_with("float"))
        {
            string_append(name, "f32", count);
            type_name.remove_prefix(5);
        }
        else if (type_name.starts_with("double"))
        {
            string_append(name, "f64", count);
            type_name.remove_prefix(6);
        }

        for (char c : type_name)
            if (c != ' ')
                name[count++] = c;

        if (add_pointer)
            name[count++] = '*';
    };

    bool add_const     = false;
    bool add_pointer   = false;
    bool add_reference = false;
    if (type_name.ends_with("* const"))
    {
        type_name.remove_suffix(7);
        add_const = true;
        add_pointer = true;
    }

    if (type_name.ends_with(" const "))
    {
        string_append(name, "const ", count);
        type_name.remove_suffix(7);
    }
    else if (type_name.ends_with(" const *"))
    {
        string_append(name, "const ", count);
        type_name.remove_suffix(8);
        add_pointer = true;
    }
    else if (type_name.ends_with("const &"))
    {
        add_const = true;
        add_reference = true;
        type_name.remove_suffix(7);
    }
    else if (type_name.ends_with(" const&"))
    {
        add_const = true;
        add_reference = true;
        type_name.remove_suffix(7);
    }
    else if (type_name.ends_with('*'))
    {
        type_name.remove_suffix(1);
        add_pointer = true;
    }
    else if (type_name.ends_with('&'))
    {
        type_name.remove_suffix(1);
        add_reference = true;
    }

    if (type_name.ends_with(' '))
        type_name.remove_suffix(1);

    if (type_name.ends_with('>'))
    {
        size_t open_angle_bracket_pos = type_name.find('<');
        append_type_name_prettified(name, type_name.substr(0, open_angle_bracket_pos), count);
        type_name.remove_prefix(open_angle_bracket_pos + 1);

        name[count++] = '<';
        size_t prev = 0;
        size_t match = 1;
        for (size_t c = 0; c < type_name.length(); ++c)
        {
            if (type_name.at(c) == '<')
            {
                ++match;
            }

            if (type_name.at(c) == '>')
            {
                --match;
                if (match <= 0)
                {
                    _name_of_parse_and_append(name, count, type_name.substr(prev, c - prev));
                    name[count++] = '>';
                    prev = c + 1;
                }
            }

            if (type_name.at(c) == ',')
            {
                _name_of_parse_and_append(name, count, type_name.substr(prev, c - prev));
                name[count++] = ',';
                prev = c + 1;
            }
        }
    }
    else
    {
        append_type_name_prettified(name, type_name, count);
    }

    if (add_pointer)
        name[count++] = '*';
    if (add_const)
        string_append(name, " const", count);
    if (add_reference)
        name[count++] = '&';
}

You can view and edit the source code on compiler explorer here.

In the next article we will discuss how to implement kind_of<T>().