Type name as a string I posted this answer recently. OP in the StackOverflow question mentioned that it was for educational use, but I can imagine it being used as a debugging aid. Then I decided to improve it slightly and make it a compile-time function. Code:

#include &lt;cassert&gt;
#include &lt;iostream&gt;
#include &lt;string_view&gt;
#include &lt;type_traits&gt;
#include &lt;vector&gt;

#ifndef __GNUC__
static_assert(false, "GCC specific");
#endif // !__GNUC__

// Finds a character in a nul terminated string.
// Returns the last character if sought one isn't found.
// Notes:
// 1. Using this because std::strchr isn't constexpr.
// `PChr` is a separate typename instead of just using Chr* to allow `Chr*` and
// `Chr const*` as the pointer.
// 2. pstr shall not be nullptr.
template &lt;typename PChr, typename Chr&gt;
[[gnu::pure, gnu::nonnull, gnu::returns_nonnull, nodiscard]]
static constexpr auto*
constexpr_strchr(PChr pstr, Chr const value) noexcept {
    // PChr must be a raw pointer type because of gnu::nonnull and
    // gnu::returns_nonnull
    static_assert(std::is_pointer_v&lt;PChr&gt;, "PChr must be a raw pointer type");
    auto constexpr nul = Chr{};
    while (*pstr != value &amp;&amp; *pstr != nul) {
        ++pstr;
    }
    return pstr;
}

// Returns distance from ptr to the end of the array.
// Notes:
// 1. ptr shall not be nullptr
// 2. ptr shall be inside the array (arr)
template &lt;typename T, auto size&gt;
[[gnu::const, gnu::artificial, gnu::nonnull, gnu::always_inline, nodiscard]]
inline static constexpr auto
distance_to_end(const T (&amp;arr)[size], T const* const ptr) noexcept {
    return arr + size - ptr;
}

// Returns type T as a string_view.
// Ex: std::string -&gt; "std::string"
template &lt;typename T&gt;
[[gnu::const, nodiscard]]
static constexpr auto type_name_finder() noexcept {
    // __PRETTY_FUNCTION__ means "$FUNCTION_SIGNATURE [with T = $TYPE]".

    // +2 here to skip "= "
    auto const* const begin = constexpr_strchr(__PRETTY_FUNCTION__, '=') + 2;

    // -2 meaning up to "]\0"
    auto const size =
            static_cast&lt;std::size_t&gt;(distance_to_end(__PRETTY_FUNCTION__, begin) - 2);
    return std::string_view{begin, size};
}

// Inline string_view with the type name.
template &lt;typename T&gt;
inline constexpr auto type_name = type_name_finder&lt;T&gt;();


// Example Class
template &lt;typename T1, typename T2&gt;
class my_class {};

int main() {
    // Example use-case
    my_class&lt;int&amp;, std::vector&lt;double&gt;&gt; my_arr[20];
    std::cout &lt;&lt; type_name&lt;decltype(my_arr)&gt;;
}


Code uses __PRETTY_FUNCTION__ in GCC and Clang to get a variable's type as a string. I would appreciate any comments on correctness and readability. I used a lot of GCC specific attributes, because __PRETTY_FUNCTION__ is GCC specific and for this reason the code can't be portable anyway.
// __PRETTY_FUNCTION__ means "$FUNCTION_SIGNATURE [with T = $TYPE]".


When you call type_name_finder(), the only part that changes in __PRETTY_FUNCTION__ is $TYPE. We can use this information to strip the static decoration surrounding that type.

template &lt;typename T&gt;
static constexpr auto type_name_finder() {
    std::string_view name, prefix, suffix;

#if defined(__clang__)
    name = __PRETTY_FUNCTION__;
    prefix = "auto type_name_finder() [T = ";
    suffix = "]";
#elif defined(__GNUC__)
    name = __PRETTY_FUNCTION__;
    prefix = "constexpr auto type_name_finder() [with T = ";
    suffix = "]";
#endif

    name.remove_prefix(prefix.size());
    name.remove_suffix(suffix.size());
    return name;
}