42

I would like to use the name of a type at compile time. For example, suppose I've written:

constexpr size_t my_strlen(const char* s)
{
        const char* cp = s;
        while(*cp != '\0') { cp++; };
        return cp - s;
}

and now I want to have:

template <typename T>
constexpr auto type_name_length = my_strlen(typeid(T).name());

But alas, typeid(T).name() is just const char*, not constexpr... is there some other, constexpr way to get a type's name?

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • What do you intend to do with `type_name_length` that you need it at compile time? Compilers are pretty good about just evaluating `strlen()` and giving you a constant if that's possible. – Barry Mar 11 '16 at 14:13
  • 1
    @Barry: I just wanted an MCVE here, so I made up a synthetic use. – einpoklum Mar 11 '16 at 14:19
  • @einpoklum That is good; but adding a comment saying that in the question (this is merely a MCVE, I am really trying to X) is also good. – Yakk - Adam Nevraumont Mar 11 '16 at 19:25
  • 1
    @Yakk: I did say "for example" and "suppose"... – einpoklum Mar 11 '16 at 19:30
  • @einpoklum I'm saying *give the real motivation* as well, at least in brief, along side the MCVE. – Yakk - Adam Nevraumont Mar 11 '16 at 19:34
  • 1
    @Yakk: It was for some CUDA device-side debugging code which prints type names but also needs to align the output, and I wanted to fit everything in a printf statement without loops to calculate lengths. – einpoklum Sep 12 '16 at 06:48

3 Answers3

112

Well, you could, sort of, but probably not quite portable:

struct string_view
{
    char const* data;
    std::size_t size;
};

inline std::ostream& operator<<(std::ostream& o, string_view const& s)
{
    return o.write(s.data, s.size);
}

template<class T>
constexpr string_view get_name()
{
    char const* p = __PRETTY_FUNCTION__;
    while (*p++ != '=');
    for (; *p == ' '; ++p);
    char const* p2 = p;
    int count = 1;
    for (;;++p2)
    {
        switch (*p2)
        {
        case '[':
            ++count;
            break;
        case ']':
            --count;
            if (!count)
                return {p, std::size_t(p2 - p)};
        }
    }
    return {};
}

And you can define your desired type_name_length as:

template <typename T>
constexpr auto type_name_length = get_name<T>().size;

DEMO (works for clang & g++)

Jamboree
  • 5,139
  • 2
  • 16
  • 36
  • 3
    Something similar could be implemented on MSVC with `__FUNCSIG__`. – melak47 Mar 11 '16 at 15:18
  • 1
    I found [this site](http://rextester.com/l/cpp_online_compiler_visual) and tested out `__FUNCSIG__`. It appears to emit the fully-substituted/bound types, as though the function were explicitly instantiated, as opposed to GCC's hybrid output. For example: `void __cdecl foo(const double &)`, which looks less helpful to me at a glance. (GCC still makes some strange distinction between dependent vs. free types, or maybe deduced vs. computed types, when performing substitutions.) I think reflection is highly underrated and under-supported, esp. with Concepts so far over the horizon... – John P Dec 26 '17 at 15:21
  • @JohnP: Do you think you could edit that into the proposed solution? – einpoklum Feb 10 '18 at 19:01
  • I don't think I know enough about the way it generates/computes the signature to make any kind of a guarantee about parsing it - this would be a better job for someone more familiar with MSVC. If you can't find anyone more suitable, I'll make an attempt. With the way it appears to work, you would expect `foo` followed by equally many opening and closing `<>` pairs - but a type could be `T (W::*operator<)(Y)`, etc. - I'm sure there are other caveats (my syntax highlighter has 'experimental' template highlighting with lots of issues, hence the hesitation.) – John P Feb 13 '18 at 05:10
  • In general I believe I would have to take a good look at the specs - obviously compilers manage somehow, and the few cases with ambiguity have special syntax to resolve it, e.g. the most vexing parse. I'd like to do it right instead of leaving an incorrect answer standing. As an aside, if there is a case for `__PRETTY_FUNCTION__` and so on, there should be a case for a `__PRETTY_THIS__` or something, right? What we're doing is 'Wrong' IMHO. – John P Feb 13 '18 at 05:17
  • Can you partially specialize this? Eg, to have `A;B` as output for a `std::pair` ? – 463035818_is_not_an_ai May 18 '19 at 09:22
  • @melak47: Do you think you could add an MSVC-oriented answer using `__FUNCSIG__`? – einpoklum Jun 13 '19 at 18:58
  • @einpoklum I'm sure I've seen an implementation around SO somewhere that just uses magic numbers to grab the substring. Since with MSVC, the pretty function name could subtly change (calling conventions changed by compiler options or x86 vs x64 for example), I scan for substrings that mark the start and end of the interesting bit which *shouldn't* change: [gist](https://gist.github.com/melak47/149f89536c76279521ca8be2fb51a531) (I use C++17 here for `std::string_view`) – melak47 Jun 14 '19 at 12:58
  • 1
    @melak47: See my answer based on your gist. Dropped the use of exceptions, I don't like having those when you can use static_assert... – einpoklum Jun 14 '19 at 14:41
12

Edit: Updated based on this answer to the non-constexpr-specific question; it is the result of refinements by several people including @HowardHinnant, @康桓瑋 @Val and myself.

The language standard does not - to my knowledge - provide any facility for obtaining type names. So, we resort to compiler-specific approaches. This works with GCC, clang and MSVC.

#include <string_view>
// If you can't use C++17's standard library, you'll need to use the GSL 
// string_view or implement your own struct (which would not be very difficult,
// since we only need a few methods here)

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);
}
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • May be 'operator ()' or 'operator const char*()' would improve this answer. – vrqq Jun 25 '21 at 15:54
  • @vrqq: Can you elaborate? – einpoklum Jun 25 '21 at 17:42
  • Yes like my answer, and there have a potential bug like 'typename' would come up twice with clang++, and string like 'class mytype' with msvc. – vrqq Jun 30 '21 at 16:58
  • 1
    Also FYI, with MSVC (only?) it prepends the function name to locally defined types, like `enum func::Local` (which is of course quite nice for diagnostics, though). – Sz. Feb 18 '22 at 22:40
1

An alternative answer that can be used in template, now it can run with g++ and clang++ and msvc.

Modified from Answer @einpoklum above: https://stackoverflow.com/a/56600402/12529885

#include <iostream>
#include <string_view>

template<typename T>
struct TypeName {
    constexpr static std::string_view fullname_intern() {
        #if defined(__clang__) || defined(__GNUC__)
            return __PRETTY_FUNCTION__;
        #elif defined(_MSC_VER)
            return __FUNCSIG__;
        #else
            #error "Unsupported compiler"
        #endif
    }
    constexpr static std::string_view name() {
        size_t prefix_len = TypeName<void>::fullname_intern().find("void");
        size_t multiple   = TypeName<void>::fullname_intern().size() - TypeName<int>::fullname_intern().size();
        size_t dummy_len  = TypeName<void>::fullname_intern().size() - 4*multiple;
        size_t target_len = (fullname_intern().size() - dummy_len)/multiple;
        std::string_view rv = fullname_intern().substr(prefix_len, target_len);
        if (rv.rfind(' ') == rv.npos)
            return rv;
        return rv.substr(rv.rfind(' ')+1);
    }

    using type = T;
    constexpr static std::string_view value = name();
};

namespace s1 {
    class MyClass;
}

//Both MSVC, G++ and Clang++ have passed test.
int main () {
    static_assert(TypeName<s1::MyClass>::value == "s1::MyClass");
    std::cout<<"FULLNAME> "<<TypeName<void>::fullname_intern()<<std::endl;
    std::cout<<"TYPETEST> '"<<TypeName<s1::MyClass>::value<<"' == 's1::MyClass'"<<std::endl;
    return 0;
}

Note that:

Full name in Clang++: static std::string_view TypeName<void>::fullname_intern() [T = void]

Full name in G++: static constexpr std::string_view TypeName<T>::fullname_intern() [with T = void; std::string_view = std::basic_string_view<char>]

Fullname in MSVC: class std::basic_string_view<char,struct std::char_traits<char> > __cdecl TypeName<void>::fullname_intern(void)(But 'class s1::MyClass' not 's1::MyClass' here)

vrqq
  • 448
  • 3
  • 8