4
\$\begingroup\$

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 <cassert>
#include <iostream>
#include <string_view>
#include <type_traits>
#include <vector>

#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 <typename PChr, typename Chr>
[[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<PChr>, "PChr must be a raw pointer type");
    auto constexpr nul = Chr{};
    while (*pstr != value && *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 <typename T, auto size>
[[gnu::const, gnu::artificial, gnu::nonnull, gnu::always_inline, nodiscard]]
inline static constexpr auto
distance_to_end(const T (&arr)[size], T const* const ptr) noexcept {
    return arr + size - ptr;
}

// Returns type T as a string_view.
// Ex: std::string -> "std::string"
template <typename T>
[[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<std::size_t>(distance_to_end(__PRETTY_FUNCTION__, begin) - 2);
    return std::string_view{begin, size};
}

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


// Example Class
template <typename T1, typename T2>
class my_class {};

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

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.

\$\endgroup\$
0

2 Answers 2

2
\$\begingroup\$

You're trying to reinvent the wheel... This specific challenge was discussed in answers to this SO question: Read HowardHinnant's long answer, then read the final, pretty, constexpr answer here. @Snowhawk quoted the code there - now you have some references and the build-up to the final form of the code.

\$\endgroup\$
1
  • \$\begingroup\$ I should have looked around before spending time on this thing. \$\endgroup\$ Commented Mar 6, 2020 at 15:40
4
\$\begingroup\$
    // __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 <typename T>
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;
}
\$\endgroup\$
4
  • \$\begingroup\$ MSVC version won't work properly because for built in types auto __cdecl type_name_finder<class does not have the "class" word. \$\endgroup\$ Commented Dec 29, 2019 at 8:16
  • \$\begingroup\$ std::string_view::remove_prefix is not noexcept. I don't think it's very appropriate to mark type_name_finder not-throwing. \$\endgroup\$ Commented Dec 29, 2019 at 8:17
  • \$\begingroup\$ Besides that, your implementation is great (+1). Thanks for this much improved version! \$\endgroup\$ Commented Dec 29, 2019 at 8:18
  • \$\begingroup\$ And what if the actual prefix or suffix isn't the one you expect? To me it looks like undefined behavior then. \$\endgroup\$ Commented Dec 29, 2019 at 9:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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