在C++中编译时获取类型名称

6

我想获取类型名称并将其打印出来以进行调试。我使用以下代码:

#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());
}

它的功能很好,但我认为存在不必要的运行时开销。是否有一种方法可以在编译时计算出可读性更高的类型标识符呢?我想到了类似于这样的东西:

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

该函数将返回一个类型名称的字符串常量。


请一次只提出一个问题。 - Lightness Races in Orbit
5个回答

7
我不认为 typeid(T).name() 会产生运行时开销。如果 expr 是多态类型,那么 typeid(expr) 会有运行时开销。
看起来解码可能是在运行时发生的,但你没有太多可以做的。如果这只是用于调试,除非你的分析器表明这导致程序变得如此缓慢,以至于调试其他元素变得困难,否则我真的不会太担心它。

是的,我在问是否可以在编译时进行符号重整。 - petersohn
无法在编译时完成名称重整。这是由于C++ ABI与操作系统通信的方式(http://en.wikipedia.org/wiki/Name_mangling#Standardised_name_mangling_in_C.2B.2B第"Standardised name mangling in C++"段)所致。 - Marcin
1
@Marcin:名称修饰在大多数ABI中都有明确定义,包括GCC遵循的Itanium ABI。不需要运行时信息;实际上,如果是这种情况,编译器怎么能产生修饰后的名称呢? - Lightness Races in Orbit
1
@Tomalak Geret'kal 我已经仔细检查了我写的内容 - 你是对的。抱歉给你带来了误导性的评论,请忽略它。 - Marcin

3
我有同样的需求,我使用一个静态方法中的_____FUNCTION_____宏来解决它。但是,您必须对_____FUNCTION_____进行一些运行时计算才能提取类名。您需要进行一些模板技巧,以避免在每个类中粘贴相同的代码。如果有人感兴趣,我可以清理并翻译我的代码从法语到英文并发布。

这种方法的主要优点是您无需启用RRTI。另一方面,类名的提取可能与编译器有关。

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();

如果你有时间的话,我很想看看这个是如何工作的。能否分享一小段代码? - afk

2

在 C++ 20 中

您可以使用标准的 std::source_location,其中它的静态方法 ::currentconsteval,这意味着您可以在编译时使用它,然后您可以获取 function_name 方法。

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>();
}

注意:源代码位置对象中的函数名称可能因编译器而异,如果您的编译器尚未支持源代码位置库,则可以使用宏__PRETTY_FUNCTION__或任何其他相关宏。

用法:

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}>

使用type_of比在typeid(...).name()中使用解构名称的优势:

(同时注意:我没有测试其他编译器的能力,因此我只保证对于GCC)

  • 您可以在编译时检查值,例如static_assert(type_of(4.0) == "double&&")是有效的。
  • 没有运行时开销。
  • 该操作可以在运行时或编译时执行(取决于给定参数是否可用于常量表达式)。
  • 它保留了CV-REF特征(constvolatile&&&)。
  • 如果类型的构造函数被删除并且不带cv-ref特征,则可以选择使用模板参数进行测试。

这可以在GCC和Clang上工作,但在MSVC上不行。MSVC仅输出函数名,就好像是通过__FUNCTION__一样。也就是说,它返回的是"func_name"而不是"func_name [with T = ...]"。 - Hedede
是的,简而言之,这是不可移植的,除非我用适当的“裁剪”替换所有编译器。 - Desmond Gold

1
你可以使用 std::type_index 来缓存解缠成字符串。

1
你可以使用 std::map 或类似的数据结构(例如 splay 树)来缓存和相对快速地访问解码名称。尽管它不是在编译时完成,但我怀疑后者是不可能的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接