开发者

Getting type names at compile time in C++ [duplicate]

This question already has answers here: Can I obtain C++ type names in a constexpr way? (3 answers) Closed 1 year ago.

I want to get the type name and print it for debug purposes. I use the following code:

#include <cxxabi.h>

inline const char* demangle(const char *s) {
    abi::__cxa_demangle(s, 0, 0, NULL);
}

template<typename T>
inline const char* type_name() {
    return demangle(typeid(T).name());
}

It works well, but it I suppose there is an unnecessary runtime overhead. Is there any way to get a human-readable form of type ids that is computed at compile time? I am thinking o开发者_如何学Pythonf something that looks like this:

boost::mpl::type_name<MyType>::value

Which would return a string constant of the type name.


I can't see typeid(T).name() incurring a runtime overhead. typeid(expr) yes, if expr is of a polymorphic type.

It looks like the demangling probably happens at runtime, but there's not an awful lot you can do about that. If this is only for debugging then I really wouldn't worry about it too much unless your profiler indicates that this is causing your program to slow down so much that debugging other elements of it is troublesome.


I have the same need, I've solved it using the _____FUNCTION_____ maccro in a static method of my class. But you must do some runtine computation on _____FUNCTION_____ to extract the class name. You have to do some template tricks to avoid paste the same code in every class. If someone is interessed I may clean and translate my code from french  to post it.

The main advantage of this method is that you don't need to enable RRTI. On the other hand, the extraction of the class name may be compiler dependant.

template <class MyT>
    class NamedClass
    {
        static std::string ComputeClassName()
        {
            std::string funcMacro=__FUNCTION__;
//compiler dependant
            static const std::string start="scul::NamedClass<class ";
            static const std::string end=">::ComputeClassName";

            return funcMacro.substr(start.size(),funcMacro.size()-end.size()-start.size());;
        }
        static const std::string _ClassName;

    };

    template <class MyT>
    const std::string NamedClass<MyT>::_ClassName=NamedClass<MyT>::ComputeClassName();


In C++ 20

You can use the standard std::source_location where its static method ::current is consteval in which you can use it at compile-time and then you can obtain the function_name method.

template <typename T>
consteval auto func_name() {
    const auto& loc = std::source_location::current();
    return loc.function_name();
}

template <typename T>
consteval std::string_view type_of_impl_() {
    constexpr std::string_view functionName = func_name<T>();
    // since func_name_ is 'consteval auto func_name() [with T = ...]'
    // we can simply get the subrange
    // because the position after the equal will never change since 
    // the same function name is used

    // another notice: these magic numbers will not work on MSVC
    return {functionName.begin() + 37, functionName.end() - 1};
}

template <typename T>
constexpr auto type_of(T&& arg) {
    return type_of_impl_<decltype(arg)>();
}

template <typename T>
constexpr auto type_of() {
    return type_of_impl_<T>();
}

Note: The function name from the source location object may vary from compiler-to-compiler and you can use the macro __PRETTY_FUNCTION__ or any other related macros if your compiler doesn't yet support the source location library.

Usage:

int x = 4;
// type_of also preserves value category and const-qualifiers
// note: it returns std::string_view
type_of(3); // int&&
type_of(x); // int&
type_of(std::as_const(x)); // const int&
type_of(std::move(x)); // int&&
type_of(const_cast<const int&&>(x)); // const int&&

struct del { del() = delete; };

type_of<del>(); // main()::del (if inside main function)
// type_of(del{}); -- error
type_of<int>(); // int
type_of<const int&>(); // const int&
type_of<std::string_view>(); // std::basic_string_view<char>
type_of([]{}); // main()::<lambda()>&&
type_of<decltype([]{})>(); // main()::<lambda()>
type_of<std::make_index_sequence<3>>(); // std::integer_sequence<long unsigned int, 0, 1, 2>

// let's assume this class template is defined outside main function:
template <auto X> struct hello {};
type_of<hello<1>>(); // hello<1>
type_of<hello<3.14f>>(); // hello<3.1400001e+0f>

// also this:
struct point { int x, y; };

type_of<hello<point{.x = 1, .y = 2}>>() // hello<point{1, 2}>

Advantage of using this type_of over demangling in typeid(...).name():

(also noted: I didn't test other compiler's ability, so I only guarantee for GCC)

  • You can check the value at compile-time, such that static_assert(type_of(4.0) == "double&&") is valid.
  • There is no runtime overhead.
  • The operation can be done either at runtime or compile-time (depending on the argument given whether it's usable in a constant expression).
  • It preserves cv-ref traits (const, volatile, & and &&).
  • You can alternatively use the template argument just in case the type's constructor is deleted and test without the cv-ref traits.


You could use std::type_index to cache the demangled strings.


You could use an std::map or similar data structure (splay trees for example) to cache and access the demangled name relatively quickly. It's not done in compile time though, I doubt the latter is possible.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜