ChatGPT解决这个技术问题 Extra ChatGPT

是否可以在标准 C++ 中打印变量的类型?

例如:

int a = 12;
cout << typeof(a) << endl;

预期输出:

int
这是 Howard 的长格式解决方案的摘要,但使用异端的单行宏实现:#define DEMANGLE_TYPEID_NAME(x) abi::__cxa_demangle(typeid((x)).name(), NULL, NULL, NULL)。如果您需要跨平台支持:使用 #ifdef#else#endif 为 MSVC 等其他平台提供一个宏。
具有更明确的人类可读要求:stackoverflow.com/questions/12877521/…
如果您仅将其用于调试,您可能需要考虑 template<typename T> void print_T() { std::cout << __PRETTY_FUNCTION__ << '\n'; }。然后使用例如 print_T<const int * const **>(); 将在运行时打印 void print_T() [T = const int *const **] 并保留所有限定符(适用于 GCC 和 Clang)。
@Henri,__PRETTY_FUNCTION__ 不是标准 C++(要求在问题标题中)。

H
Howard Hinnant

C++11 更新了一个非常古老的问题:在 C++ 中打印变量类型。

公认的(也是好的)答案是使用 typeid(a).name(),其中 a 是一个变量名。

现在在 C++11 中,我们有 decltype(x),它可以将表达式转换为类型。 decltype() 有自己的一套非常有趣的规则。例如 decltype(a)decltype((a)) 通常是不同的类型(一旦这些原因暴露出来,就会有充分且易于理解的原因)。

我们值得信赖的typeid(a).name()会帮助我们探索这个勇敢的新世界吗?

不。

但是那个工具并没有那么复杂。这就是我用来回答这个问题的工具。我会将这个新工具与 typeid(a).name() 进行比较和对比。而这个新工具实际上是在 typeid(a).name() 之上构建的。

根本问题:

typeid(a).name()

丢弃 cv 限定符、引用和左值/右值。例如:

const int ci = 0;
std::cout << typeid(ci).name() << '\n';

对我来说输出:

i

我在猜测 MSVC 输出:

int

const 不见了。这不是 QOI(实施质量)问题。该标准规定了这种行为。

我在下面推荐的是:

template <typename T> std::string type_name();

这将像这样使用:

const int ci = 0;
std::cout << type_name<decltype(ci)>() << '\n';

对我来说输出:

int const

<disclaimer> 我没有在 MSVC 上测试过。 </disclaimer> 但我欢迎那些这样做的人提供反馈。

C++11 解决方案

根据 ipapadop 在他对 demangle 类型的回答中的建议,我将 __cxa_demangle 用于非 MSVC 平台。但在 MSVC 上,我相信 typeid 可以解开名称(未经测试)。这个核心围绕着一些简单的测试来检测、恢复和报告 cv-qualifiers 和对输入类型的引用。

#include <type_traits>
#include <typeinfo>
#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <class T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
}

结果

有了这个解决方案,我可以做到这一点:

int& foo_lref();
int&& foo_rref();
int foo_value();

int
main()
{
    int i = 0;
    const int ci = 0;
    std::cout << "decltype(i) is " << type_name<decltype(i)>() << '\n';
    std::cout << "decltype((i)) is " << type_name<decltype((i))>() << '\n';
    std::cout << "decltype(ci) is " << type_name<decltype(ci)>() << '\n';
    std::cout << "decltype((ci)) is " << type_name<decltype((ci))>() << '\n';
    std::cout << "decltype(static_cast<int&>(i)) is " << type_name<decltype(static_cast<int&>(i))>() << '\n';
    std::cout << "decltype(static_cast<int&&>(i)) is " << type_name<decltype(static_cast<int&&>(i))>() << '\n';
    std::cout << "decltype(static_cast<int>(i)) is " << type_name<decltype(static_cast<int>(i))>() << '\n';
    std::cout << "decltype(foo_lref()) is " << type_name<decltype(foo_lref())>() << '\n';
    std::cout << "decltype(foo_rref()) is " << type_name<decltype(foo_rref())>() << '\n';
    std::cout << "decltype(foo_value()) is " << type_name<decltype(foo_value())>() << '\n';
}

输出是:

decltype(i) is int
decltype((i)) is int&
decltype(ci) is int const
decltype((ci)) is int const&
decltype(static_cast<int&>(i)) is int&
decltype(static_cast<int&&>(i)) is int&&
decltype(static_cast<int>(i)) is int
decltype(foo_lref()) is int&
decltype(foo_rref()) is int&&
decltype(foo_value()) is int

请注意(例如)decltype(i)decltype((i)) 之间的区别。前者是i声明的类型。后者是表达式 i 的“类型”。 (表达式从不具有引用类型,但作为约定 decltype 表示具有左值引用的左值表达式)。

因此,除了探索和调试您自己的代码之外,此工具是了解 decltype 的绝佳工具。

相反,如果我仅在 typeid(a).name() 上构建它,而不添加丢失的 cv 限定符或引用,则输出将是:

decltype(i) is int
decltype((i)) is int
decltype(ci) is int
decltype((ci)) is int
decltype(static_cast<int&>(i)) is int
decltype(static_cast<int&&>(i)) is int
decltype(static_cast<int>(i)) is int
decltype(foo_lref()) is int
decltype(foo_rref()) is int
decltype(foo_value()) is int

即每个引用和 cv 限定符都被剥离。

C++14 更新

就在您认为自己已经找到了解决问题的方法时,总会有人突然冒出来,向您展示更好的方法。 :-)

Jamboree 中的 This answer 展示了如何在编译时获取 C++14 中的类型名称。这是一个绝妙的解决方案,原因如下:

它在编译时!您可以让编译器本身而不是库(甚至是 std::lib)来完成这项工作。这意味着最新语言功能(如 lambdas)的结果更准确。

Jamboree's answer 并没有为 VS 安排好一切,我正在稍微调整他的代码。但是由于这个答案得到了很多意见,所以花一些时间去那里并支持他的答案,没有它,这个更新永远不会发生。

#include <cstddef>
#include <stdexcept>
#include <cstring>
#include <ostream>

#ifndef _MSC_VER
#  if __cplusplus < 201103
#    define CONSTEXPR11_TN
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN
#  elif __cplusplus < 201402
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN noexcept
#  else
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN constexpr
#    define NOEXCEPT_TN noexcept
#  endif
#else  // _MSC_VER
#  if _MSC_VER < 1900
#    define CONSTEXPR11_TN
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN
#  elif _MSC_VER < 2000
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN noexcept
#  else
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN constexpr
#    define NOEXCEPT_TN noexcept
#  endif
#endif  // _MSC_VER

class static_string
{
    const char* const p_;
    const std::size_t sz_;

public:
    typedef const char* const_iterator;

    template <std::size_t N>
    CONSTEXPR11_TN static_string(const char(&a)[N]) NOEXCEPT_TN
        : p_(a)
        , sz_(N-1)
        {}

    CONSTEXPR11_TN static_string(const char* p, std::size_t N) NOEXCEPT_TN
        : p_(p)
        , sz_(N)
        {}

    CONSTEXPR11_TN const char* data() const NOEXCEPT_TN {return p_;}
    CONSTEXPR11_TN std::size_t size() const NOEXCEPT_TN {return sz_;}

    CONSTEXPR11_TN const_iterator begin() const NOEXCEPT_TN {return p_;}
    CONSTEXPR11_TN const_iterator end()   const NOEXCEPT_TN {return p_ + sz_;}

    CONSTEXPR11_TN char operator[](std::size_t n) const
    {
        return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
    }
};

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

template <class T>
CONSTEXPR14_TN
static_string
type_name()
{
#ifdef __clang__
    static_string p = __PRETTY_FUNCTION__;
    return static_string(p.data() + 31, p.size() - 31 - 1);
#elif defined(__GNUC__)
    static_string p = __PRETTY_FUNCTION__;
#  if __cplusplus < 201402
    return static_string(p.data() + 36, p.size() - 36 - 1);
#  else
    return static_string(p.data() + 46, p.size() - 46 - 1);
#  endif
#elif defined(_MSC_VER)
    static_string p = __FUNCSIG__;
    return static_string(p.data() + 38, p.size() - 38 - 7);
#endif
}

如果您仍停留在古老的 C++11 中,此代码将在 constexpr 上自动退避。如果你用 C++98/03 在洞壁上绘画,noexcept 也会被牺牲掉。

C++17 更新

在下面的评论中 Lyberta 指出新的 std::string_view 可以替换 static_string

template <class T>
constexpr
std::string_view
type_name()
{
    using namespace std;
#ifdef __clang__
    string_view p = __PRETTY_FUNCTION__;
    return string_view(p.data() + 34, p.size() - 34 - 1);
#elif defined(__GNUC__)
    string_view p = __PRETTY_FUNCTION__;
#  if __cplusplus < 201402
    return string_view(p.data() + 36, p.size() - 36 - 1);
#  else
    return string_view(p.data() + 49, p.find(';', 49) - 49);
#  endif
#elif defined(_MSC_VER)
    string_view p = __FUNCSIG__;
    return string_view(p.data() + 84, p.size() - 84 - 7);
#endif
}

由于 Jive Dadson 在下面的评论中所做的非常出色的侦探工作,我已经更新了 VS 的常量。

更新:

请务必查看 this rewrite below,它消除了我最新公式中不可读的幻数。


VS 14 CTP 打印出正确的类型,我只需要添加一个 #include <iostream> 行。
为什么使用模板std::string type_name()?为什么不将类型作为参数传递?
我相信我的理由是有时我只有有一个类型(例如推导的模板参数),我不想人为地构造其中一个来获取类型(尽管这些天declval 会完成这项工作)。
@AngelusMortis:因为与 C++ 代码相比,英语含糊不清,我鼓励您使用您感兴趣的特定类型和您感兴趣的特定编译器将其复制/粘贴到您的测试用例中,并回写更多如果结果令人惊讶和/或不令人满意,请详细说明。
@HowardHinnant 你能用 std::string_view 代替 static_string 吗?
K
Konrad Rudolph

尝试:

#include <typeinfo>

// …
std::cout << typeid(a).name() << '\n';

您可能必须在编译器选项中激活 RTTI 才能使其正常工作。此外,它的输出取决于编译器。它可能是原始类型名称或名称修饰符号或介于两者之间的任何内容。


为什么 name() 函数返回的字符串是实现定义的?
@PravasiMeet 据我所知,没有充分的理由。委员会只是不想强迫编译器实现者进入特定的技术方向——事后看来,这可能是一个错误。
有没有可以用来启用 RTTI 的标志?也许你可以让你的答案具有包容性。
@Destructor 提供标准化的名称修饰格式可能会给人一种印象,即由两个不同编译器构建的二进制文件之间的互操作性是可能的和/或安全的,但实际上并非如此。因为 C++ 没有标准的 ABI,所以标准的名称修改方案将毫无意义,并且可能具有误导性和危险性。
@Jim关于编译器标志的部分将比答案本身长一个数量级。 GCC 默认使用它进行编译,因此“-fno-rtti”,其他编译器可能选择不使用,但没有编译器标志的标准。
康桓瑋

根据Howard的解决方案,如果您不喜欢幻数,我认为这是一种很好的表示方式,并且看起来很直观:

#include <string_view>

template <typename T>
constexpr auto type_name() {
  std::string_view name, prefix, suffix;
#ifdef __clang__
  name = __PRETTY_FUNCTION__;
  prefix = "auto type_name() [T = ";
  suffix = "]";
#elif defined(__GNUC__)
  name = __PRETTY_FUNCTION__;
  prefix = "constexpr auto type_name() [with T = ";
  suffix = "]";
#elif defined(_MSC_VER)
  name = __FUNCSIG__;
  prefix = "auto __cdecl type_name<";
  suffix = ">(void)";
#endif
  name.remove_prefix(prefix.size());
  name.remove_suffix(suffix.size());
  return name;
}

Demo.


这是对过去几个 C++ 版本所做的努力的极大提炼,使之变得简短而甜蜜。 +1。
这也是我的最爱!
这是我使用的一个类似的函数,它自动检测后缀/前缀:stackoverflow.com/questions/1055452/…
字符串前缀/后缀和幻数很容易发生变化。函数 stackoverflow.com/a/58331141/7163942 自动计算它们。
@Val:请在此处将您的解决方案作为单独的答案发布。
N
Nurbol Alpysbayev

非常难看,但如果您只想要编译时间信息(例如用于调试),则可以解决问题:

auto testVar = std::make_tuple(1, 1.0, "abc");
decltype(testVar)::foo= 1;

回报:

Compilation finished with errors:
source.cpp: In function 'int main()':
source.cpp:5:19: error: 'foo' is not a member of 'std::tuple<int, double, const char*>'

只有 c++ 可以使这变得如此困难(在编译时打印自动变量类型)。只有 C++。
@KarlP 公平地说,这有点令人费解,这也有效:) auto testVar = std::make_tuple(1, 1.0, "abc"); decltype(testVar)::foo = 1;
在 VC++17 上,这会将右值引用减少为普通引用,即使在带有 forwarding-reference 参数的模板函数中,并且对象名称包装在 std::forward 中。
您无需创建任何新轮子就可以使用该类型!
此技术也在 Effective Modern C++ 中的“第 4 项:知道如何查看推导的类型”中进行了描述
B
BartoszKP

不要忘记包括 <typeinfo>

我相信您指的是运行时类型识别。您可以通过执行上述操作来实现。

#include <iostream>
#include <typeinfo>

using namespace std;

int main() {
  int i;
  cout << typeid(i).name();
  return 0;
}

p
paercebal

请注意,由 C++ 的 RTTI 功能生成的名称不可移植。例如,类

MyNamespace::CMyContainer<int, test_MyNamespace::CMyObject>

将具有以下名称:

// MSVC 2003:
class MyNamespace::CMyContainer[int,class test_MyNamespace::CMyObject]
// G++ 4.2:
N8MyNamespace8CMyContainerIiN13test_MyNamespace9CMyObjectEEE

因此,您不能使用此信息进行序列化。但是,typeid(a).name() 属性仍然可以用于日志/调试目的


N
Nick

您可以使用模板。

template <typename T> const char* typeof(T&) { return "unknown"; }    // default
template<> const char* typeof(int&) { return "int"; }
template<> const char* typeof(float&) { return "float"; }

在上面的示例中,当类型不匹配时,它将打印“未知”。


它不会为短裤和字符打印“int”吗?和双打的“浮动”?
@gartenriese 专业化没有这个缺点。对于 double,它将编译模板函数的非特化版本,而不是进行隐式类型转换以使用特化:cpp.sh/2wzc
@chappjc:老实说,我当时不知道为什么这么问,现在我很清楚了。但无论如何,感谢您回答一年前的问题!
@gartenriese 我也想多了,但“互联网”在某些时候可能会有同样的问题。
i
ipapadop

如前所述,typeid().name() 可能会返回一个损坏的名称。在 GCC(和其他一些编译器)中,您可以使用以下代码解决它:

#include <cxxabi.h>
#include <iostream>
#include <typeinfo>
#include <cstdlib>

namespace some_namespace { namespace another_namespace {

  class my_class { };

} }

int main() {
  typedef some_namespace::another_namespace::my_class my_type;
  // mangled
  std::cout << typeid(my_type).name() << std::endl;

  // unmangled
  int status = 0;
  char* demangled = abi::__cxa_demangle(typeid(my_type).name(), 0, 0, &status);

  switch (status) {
    case -1: {
      // could not allocate memory
      std::cout << "Could not allocate memory" << std::endl;
      return -1;
    } break;
    case -2: {
      // invalid name under the C++ ABI mangling rules
      std::cout << "Invalid name" << std::endl;
      return -1;
    } break;
    case -3: {
      // invalid argument
      std::cout << "Invalid argument to demangle()" << std::endl;
      return -1;
    } break;
 }
 std::cout << demangled << std::endl;

 free(demangled);

 return 0;

}


V
Val

Howard Hinnant 使用幻数提取类型名称。 康桓瑋 建议的字符串前缀和后缀。但是前缀/后缀不断变化。使用“probe_type” type_name 自动计算“probe_type”的前缀和后缀大小以提取类型名称:

#include <string_view>
using namespace std;

namespace typeName {
 template <typename T>
  constexpr string_view wrapped_type_name () {
#ifdef __clang__
    return __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
    return  __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
    return  __FUNCSIG__;
#endif
  }

  class probe_type;
  constexpr string_view probe_type_name ("typeName::probe_type");
  constexpr string_view probe_type_name_elaborated ("class typeName::probe_type");
  constexpr string_view probe_type_name_used (wrapped_type_name<probe_type> ().find (probe_type_name_elaborated) != -1 ? probe_type_name_elaborated : probe_type_name);

  constexpr size_t prefix_size () {
    return wrapped_type_name<probe_type> ().find (probe_type_name_used);
  }

  constexpr size_t suffix_size () {
    return wrapped_type_name<probe_type> ().length () - prefix_size () - probe_type_name_used.length ();
  }

  template <typename T>
  string_view type_name () {
    constexpr auto type_name = wrapped_type_name<T> ();

    return type_name.substr (prefix_size (), type_name.length () - prefix_size () - suffix_size ());
  }
}

#include <iostream>

using typeName::type_name;
using typeName::probe_type;

class test;

int main () {
  cout << type_name<class test> () << endl;

  cout << type_name<const int*&> () << endl;
  cout << type_name<unsigned int> () << endl;

  const int ic = 42;
  const int* pic = &ic;
  const int*& rpic = pic;
  cout << type_name<decltype(ic)> () << endl;
  cout << type_name<decltype(pic)> () << endl;
  cout << type_name<decltype(rpic)> () << endl;

  cout << type_name<probe_type> () << endl;
}

输出

gcc 10.2

test
const int *&
unsigned int
const int
const int *
const int *&
typeName::probe_type

clang 11.0.0

test
const int *&
unsigned int
const int
const int *
const int *&
typeName::probe_type

VS 2019 版本 16.7.6:

class test
const int*&
unsigned int
const int
const int*
const int*&
class typeName::probe_type

很好的解决方案!然而,class_specifier 缺少一个尾随空格,因此前缀大小因比应有的小一个字符而被错误计算。无法自己编辑帖子,因为更改太小了。然后应该相应地更改编译器输出。
实际上,您所做的假设与@康桓玮没有本质上的不同:假设 msvc 有一个额外的“class”前缀,而 GCC 和 clang 则不存在。如果前缀和后缀可以改变,那也可以。所以不清楚这是否比仅仅对整个前缀做出假设更好。
...无论如何,您介意在下面查看我自己建议的解决方案吗?告诉我是否需要对 MSVC 进行一些额外的调整?
@einpoklum:从魔术数字到魔术字符串再到使用预定义类型(带有编译器假设)的计算字符串可以减少代码的脆弱性。要沿着这条路线前进,请注意编译器使用类型名称(<probe_type_name>)或详细类型说明符(class <probe_type_name>),而不是假设哪个编译器做了什么让计算包装文本并确定具体用法(上面的修改代码)并使用它.对于第二个问题:该代码在 MSVC Visual Studio 2019 版本 16.7.6 上运行良好。 (在 MSVC 的定向网站上失败)。
@康桓玮:对probe_type 使用void(或int)效果很好。但它假定前缀不包含这些名称。通过类/命名空间名称自定义更多上下文似乎不那么脆弱。
G
Greg Hewgill

您可以为此使用特征类。就像是:

#include <iostream>
using namespace std;

template <typename T> class type_name {
public:
    static const char *name;
};

#define DECLARE_TYPE_NAME(x) template<> const char *type_name<x>::name = #x;
#define GET_TYPE_NAME(x) (type_name<typeof(x)>::name)

DECLARE_TYPE_NAME(int);

int main()
{
    int a = 12;
    cout << GET_TYPE_NAME(a) << endl;
}

DECLARE_TYPE_NAME 定义的存在是为了让您更轻松地为您期望需要的所有类型声明此特征类。

这可能比涉及 typeid 的解决方案更有用,因为您可以控制输出。例如,在我的编译器上将 typeid 用于 long long 会得到“x”。


e
einpoklum

@康桓瑋's answer 的另一种看法(最初),对前缀和后缀细节做更少的假设,并受到 @Val's answer 的启发——但不会污染全局命名空间;无条件;并且希望更容易阅读。

流行的编译器提供了一个带有当前函数签名的宏。现在,函数是可模板化的;所以签名包含模板参数。因此,基本方法是:给定一个类型,在一个函数中,将该类型作为模板参数。

不幸的是,类型名称包含在描述函数的文本中,这在编译器之间是不同的。例如,对于 GCC,类型为 doubletemplate <typename T> int foo() 的签名是:int foo() [T = double]

那么,如何摆脱包装文本呢? @HowardHinnant 的解决方案是最短和最“直接”的:只需使用每个编译器的幻数来删除前缀和后缀。但显然,这很脆弱;没有人喜欢他们代码中的幻数。然而,事实证明,给定具有已知名称的类型的宏值,您可以确定构成包装的前缀和后缀。

#include <string_view>

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

GodBolt 上查看。这也应该适用于 MSVC。


用宏和前缀/后缀操作隔离代码的伟大决定。
@Val:我基本上只是想遵循“最小惊讶原则”。
J
Jonas

在 C++11 中,我们有 decltype。标准 C++ 中无法显示使用 decltype 声明的变量的确切类型。我们可以使用 boost typeindex 即 type_id_with_cvr(cvr 代表 const、volatile、reference)来打印如下所示的类型。

#include <iostream>
#include <boost/type_index.hpp>

using namespace std;
using boost::typeindex::type_id_with_cvr;

int main() {
  int i = 0;
  const int ci = 0;
  cout << "decltype(i) is " << type_id_with_cvr<decltype(i)>().pretty_name() << '\n';
  cout << "decltype((i)) is " << type_id_with_cvr<decltype((i))>().pretty_name() << '\n';
  cout << "decltype(ci) is " << type_id_with_cvr<decltype(ci)>().pretty_name() << '\n';
  cout << "decltype((ci)) is " << type_id_with_cvr<decltype((ci))>().pretty_name() << '\n';
  cout << "decltype(std::move(i)) is " << type_id_with_cvr<decltype(std::move(i))>().pretty_name() << '\n';
  cout << "decltype(std::static_cast<int&&>(i)) is " << type_id_with_cvr<decltype(static_cast<int&&>(i))>().pretty_name() << '\n';
  return 0;
}

使用辅助函数会更简单吗:template<typename T> void print_type(T){cout << "type T is: "<< type_id_with_cvr<T>().pretty_name()<< '\n';}
A
Alan

您也可以使用带有选项 -t (type) 的 c++filt 来解开类型名称:

#include <iostream>
#include <typeinfo>
#include <string>

using namespace std;

int main() {
  auto x = 1;
  string my_type = typeid(x).name();
  system(("echo " + my_type + " | c++filt -t").c_str());
  return 0;
}

仅在 linux 上测试。


地狱丑陋,但会做我需要的。并且比其他解决方案小得多。顺便说一句,在 Mac 上工作。
J
James Hopkin

涉及 RTTI (typeid) 的其他答案可能是您想要的,只要:

您可以承受内存开销(对于某些编译器来说可能相当大)

编译器返回的类名很有用

另一种方法(类似于 Greg Hewgill 的回答)是建立一个特性的编译时间表。

template <typename T> struct type_as_string;

// declare your Wibble type (probably with definition of Wibble)
template <>
struct type_as_string<Wibble>
{
    static const char* const value = "Wibble";
};

请注意,如果您将声明包装在宏中,则由于逗号的原因,您将难以为采用多个参数(例如 std::map)的模板类型声明名称。

要访问变量类型的名称,您只需要

template <typename T>
const char* get_type_as_string(const T&)
{
    return type_as_string<T>::value;
}

关于逗号的好点,我知道宏是一个坏主意是有原因的,但当时没有想到!
静态 const char* value = "wibble";你不能做那个伙伴:)
J
Jahid

没有函数重载的更通用的解决方案比我以前的解决方案:

template<typename T>
std::string TypeOf(T){
    std::string Type="unknown";
    if(std::is_same<T,int>::value) Type="int";
    if(std::is_same<T,std::string>::value) Type="String";
    if(std::is_same<T,MyClass>::value) Type="MyClass";

    return Type;}

这里 MyClass 是用户定义的类。这里也可以添加更多条件。

例子:

#include <iostream>



class MyClass{};


template<typename T>
std::string TypeOf(T){
    std::string Type="unknown";
    if(std::is_same<T,int>::value) Type="int";
    if(std::is_same<T,std::string>::value) Type="String";
    if(std::is_same<T,MyClass>::value) Type="MyClass";
    return Type;}


int main(){;
    int a=0;
    std::string s="";
    MyClass my;
    std::cout<<TypeOf(a)<<std::endl;
    std::cout<<TypeOf(s)<<std::endl;
    std::cout<<TypeOf(my)<<std::endl;

    return 0;}

输出:

int
String
MyClass

J
Jahid

我喜欢尼克的方法,一个完整的形式可能是这样的(对于所有基本数据类型):

template <typename T> const char* typeof(T&) { return "unknown"; }    // default
template<> const char* typeof(int&) { return "int"; }
template<> const char* typeof(short&) { return "short"; }
template<> const char* typeof(long&) { return "long"; }
template<> const char* typeof(unsigned&) { return "unsigned"; }
template<> const char* typeof(unsigned short&) { return "unsigned short"; }
template<> const char* typeof(unsigned long&) { return "unsigned long"; }
template<> const char* typeof(float&) { return "float"; }
template<> const char* typeof(double&) { return "double"; }
template<> const char* typeof(long double&) { return "long double"; }
template<> const char* typeof(std::string&) { return "String"; }
template<> const char* typeof(char&) { return "char"; }
template<> const char* typeof(signed char&) { return "signed char"; }
template<> const char* typeof(unsigned char&) { return "unsigned char"; }
template<> const char* typeof(char*&) { return "char*"; }
template<> const char* typeof(signed char*&) { return "signed char*"; }
template<> const char* typeof(unsigned char*&) { return "unsigned char*"; }

(i) 它不适用于其他类型(即根本不是通用的); (ii) 无用的代码膨胀; (iii) typeiddecltype 可以(正确地)完成相同的操作。
你是对的,但它涵盖了所有基本类型......这就是我现在需要的......
你能告诉我,你会怎么用 decltype 来做,
如果是编译时测试,可以使用 std::is_same 和 decltype 来获取 T 和 S。
H
HolyBlackCat

当我挑战时,我决定测试一个与平台无关的(希望是)模板诡计能走多远。

这些名称在编译时完全组装。 (这意味着无法使用 typeid(T).name(),因此您必须显式提供非复合类型的名称。否则将显示占位符。)

示例用法:

TYPE_NAME(int)
TYPE_NAME(void)
// You probably should list all primitive types here.

TYPE_NAME(std::string)

int main()
{
    // A simple case
    std::cout << type_name<void(*)(int)> << '\n';
    // -> `void (*)(int)`

    // Ugly mess case
    // Note that compiler removes cv-qualifiers from parameters and replaces arrays with pointers.
    std::cout << type_name<void (std::string::*(int[3],const int, void (*)(std::string)))(volatile int*const*)> << '\n';
    // -> `void (std::string::*(int *,int,void (*)(std::string)))(volatile int *const*)`

    // A case with undefined types
    //  If a type wasn't TYPE_NAME'd, it's replaced by a placeholder, one of `class?`, `union?`, `enum?` or `??`.
    std::cout << type_name<std::ostream (*)(int, short)> << '\n';
    // -> `class? (*)(int,??)`
    // With appropriate TYPE_NAME's, the output would be `std::string (*)(int,short)`.
}

代码:

#include <type_traits>
#include <utility>

static constexpr std::size_t max_str_lit_len = 256;

template <std::size_t I, std::size_t N> constexpr char sl_at(const char (&str)[N])
{
    if constexpr(I < N)
        return str[I];
    else
        return '\0';
}

constexpr std::size_t sl_len(const char *str)
{
    for (std::size_t i = 0; i < max_str_lit_len; i++)
        if (str[i] == '\0')
            return i;
    return 0;
}

template <char ...C> struct str_lit
{
    static constexpr char value[] {C..., '\0'};
    static constexpr int size = sl_len(value);

    template <typename F, typename ...P> struct concat_impl {using type = typename concat_impl<F>::type::template concat_impl<P...>::type;};
    template <char ...CC> struct concat_impl<str_lit<CC...>> {using type = str_lit<C..., CC...>;};
    template <typename ...P> using concat = typename concat_impl<P...>::type;
};

template <typename, const char *> struct trim_str_lit_impl;
template <std::size_t ...I, const char *S> struct trim_str_lit_impl<std::index_sequence<I...>, S>
{
    using type = str_lit<S[I]...>;
};
template <std::size_t N, const char *S> using trim_str_lit = typename trim_str_lit_impl<std::make_index_sequence<N>, S>::type;

#define STR_LIT(str) ::trim_str_lit<::sl_len(str), ::str_lit<STR_TO_VA(str)>::value>
#define STR_TO_VA(str) STR_TO_VA_16(str,0),STR_TO_VA_16(str,16),STR_TO_VA_16(str,32),STR_TO_VA_16(str,48)
#define STR_TO_VA_16(str,off) STR_TO_VA_4(str,0+off),STR_TO_VA_4(str,4+off),STR_TO_VA_4(str,8+off),STR_TO_VA_4(str,12+off)
#define STR_TO_VA_4(str,off) ::sl_at<off+0>(str),::sl_at<off+1>(str),::sl_at<off+2>(str),::sl_at<off+3>(str)

template <char ...C> constexpr str_lit<C...> make_str_lit(str_lit<C...>) {return {};}
template <std::size_t N> constexpr auto make_str_lit(const char (&str)[N])
{
    return trim_str_lit<sl_len((const char (&)[N])str), str>{};
}

template <std::size_t A, std::size_t B> struct cexpr_pow {static constexpr std::size_t value = A * cexpr_pow<A,B-1>::value;};
template <std::size_t A> struct cexpr_pow<A,0> {static constexpr std::size_t value = 1;};
template <std::size_t N, std::size_t X, typename = std::make_index_sequence<X>> struct num_to_str_lit_impl;
template <std::size_t N, std::size_t X, std::size_t ...Seq> struct num_to_str_lit_impl<N, X, std::index_sequence<Seq...>>
{
    static constexpr auto func()
    {
        if constexpr (N >= cexpr_pow<10,X>::value)
            return num_to_str_lit_impl<N, X+1>::func();
        else
            return str_lit<(N / cexpr_pow<10,X-1-Seq>::value % 10 + '0')...>{};
    }
};
template <std::size_t N> using num_to_str_lit = decltype(num_to_str_lit_impl<N,1>::func());


using spa = str_lit<' '>;
using lpa = str_lit<'('>;
using rpa = str_lit<')'>;
using lbr = str_lit<'['>;
using rbr = str_lit<']'>;
using ast = str_lit<'*'>;
using amp = str_lit<'&'>;
using con = str_lit<'c','o','n','s','t'>;
using vol = str_lit<'v','o','l','a','t','i','l','e'>;
using con_vol = con::concat<spa, vol>;
using nsp = str_lit<':',':'>;
using com = str_lit<','>;
using unk = str_lit<'?','?'>;

using c_cla = str_lit<'c','l','a','s','s','?'>;
using c_uni = str_lit<'u','n','i','o','n','?'>;
using c_enu = str_lit<'e','n','u','m','?'>;

template <typename T> inline constexpr bool ptr_or_ref = std::is_pointer_v<T> || std::is_reference_v<T> || std::is_member_pointer_v<T>;
template <typename T> inline constexpr bool func_or_arr = std::is_function_v<T> || std::is_array_v<T>;

template <typename T> struct primitive_type_name {using value = unk;};

template <typename T, typename = std::enable_if_t<std::is_class_v<T>>> using enable_if_class = T;
template <typename T, typename = std::enable_if_t<std::is_union_v<T>>> using enable_if_union = T;
template <typename T, typename = std::enable_if_t<std::is_enum_v <T>>> using enable_if_enum  = T;
template <typename T> struct primitive_type_name<enable_if_class<T>> {using value = c_cla;};
template <typename T> struct primitive_type_name<enable_if_union<T>> {using value = c_uni;};
template <typename T> struct primitive_type_name<enable_if_enum <T>> {using value = c_enu;};

template <typename T> struct type_name_impl;

template <typename T> using type_name_lit = std::conditional_t<std::is_same_v<typename primitive_type_name<T>::value::template concat<spa>,
                                                                               typename type_name_impl<T>::l::template concat<typename type_name_impl<T>::r>>,
                                            typename primitive_type_name<T>::value,
                                            typename type_name_impl<T>::l::template concat<typename type_name_impl<T>::r>>;
template <typename T> inline constexpr const char *type_name = type_name_lit<T>::value;

template <typename T, typename = std::enable_if_t<!std::is_const_v<T> && !std::is_volatile_v<T>>> using enable_if_no_cv = T;

template <typename T> struct type_name_impl
{
    using l = typename primitive_type_name<T>::value::template concat<spa>;
    using r = str_lit<>;
};
template <typename T> struct type_name_impl<const T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<con>,
                                 con::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<volatile T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<vol>,
                                 vol::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<const volatile T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<con_vol>,
                                 con_vol::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<T *>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, ast>,
                                 typename type_name_impl<T>::l::template concat<     ast>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T &>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, amp>,
                                 typename type_name_impl<T>::l::template concat<     amp>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T &&>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, amp, amp>,
                                 typename type_name_impl<T>::l::template concat<     amp, amp>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T, typename C> struct type_name_impl<T C::*>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, type_name_lit<C>, nsp, ast>,
                                 typename type_name_impl<T>::l::template concat<     type_name_lit<C>, nsp, ast>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<enable_if_no_cv<T[]>>
{
    using l = typename type_name_impl<T>::l;
    using r = lbr::concat<rbr, typename type_name_impl<T>::r>;
};
template <typename T, std::size_t N> struct type_name_impl<enable_if_no_cv<T[N]>>
{
    using l = typename type_name_impl<T>::l;
    using r = lbr::concat<num_to_str_lit<N>, rbr, typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T()>
{
    using l = typename type_name_impl<T>::l;
    using r = lpa::concat<rpa, typename type_name_impl<T>::r>;
};
template <typename T, typename P1, typename ...P> struct type_name_impl<T(P1, P...)>
{
    using l = typename type_name_impl<T>::l;
    using r = lpa::concat<type_name_lit<P1>,
                          com::concat<type_name_lit<P>>..., rpa, typename type_name_impl<T>::r>;
};

#define TYPE_NAME(t) template <> struct primitive_type_name<t> {using value = STR_LIT(#t);};

M
Milo Lu

正如 Scott Meyers 在 Effective Modern C++ 中所解释的,

对 std::type_info::name 的调用不保证返回任何合理的。

最好的解决方案是让编译器在类型推导过程中产生错误信息,例如,

template<typename T>
class TD;

int main(){
    const int theAnswer = 32;
    auto x = theAnswer;
    auto y = &theAnswer;
    TD<decltype(x)> xType;
    TD<decltype(y)> yType;
    return 0;
}

结果将是这样的,具体取决于编译器,

test4.cpp:10:21: error: aggregate ‘TD<int> xType’ has incomplete type and cannot be defined TD<decltype(x)> xType;

test4.cpp:11:21: error: aggregate ‘TD<const int *> yType’ has incomplete type and cannot be defined TD<decltype(y)> yType;

因此,我们知道x的类型是inty的类型是const int*


C
CPP_is_no_STANDARD

考虑这段代码:

#include <iostream>

int main()
{
    char a = 2; // Will print the type "char"
    std::string s = typeid(a).name(); // Store it in a string
    
    if(s == "f") 
    {
        std::cout<<"float";
    }
    
    else if(s == "d")
    {
        std::cout<<"double";
    }
    
    else if(s == "i")
    {
        std::cout<<"int";
    }
    
    else if(s == "c") // Because typeid(a).name(); would print c only, so to make it more concise we should print char
    {
        std::cout<<"char";
    }
    
    return 0;
}

我相信您想要整个单词(而不是只打印 int 的缩写形式(即 i),您想要 int),这就是我做 if 的原因。为什么我不立即做

if(typeid(a).name() == "c")

简而言之,这是行不通的。您需要将其存储在字符串中。

在我看来,在这种情况下,Python 比 C++ 更好。 Python 有 type 函数可以直接访问变量的类型。


G
Graywolf
#include <iostream>
#include <typeinfo>
using namespace std;
#define show_type_name(_t) \
    system(("echo " + string(typeid(_t).name()) + " | c++filt -t").c_str())

int main() {
    auto a = {"one", "two", "three"};
    cout << "Type of a: " << typeid(a).name() << endl;
    cout << "Real type of a:\n";
    show_type_name(a);
    for (auto s : a) {
        if (string(s) == "one") {
            cout << "Type of s: " << typeid(s).name() << endl;
            cout << "Real type of s:\n";
            show_type_name(s);
        }
        cout << s << endl;
    }

    int i = 5;
    cout << "Type of i: " << typeid(i).name() << endl;
    cout << "Real type of i:\n";
    show_type_name(i);
    return 0;
}

输出:

Type of a: St16initializer_listIPKcE
Real type of a:
std::initializer_list<char const*>
Type of s: PKc
Real type of s:
char const*
one
two
three
Type of i: i
Real type of i:
int

L
Lars Melchior

对于仍在访问的任何人,我最近遇到了同样的问题,并决定根据这篇文章的答案编写一个小型库。它提供 constexpr 类型名称和类型索引,并在 Mac、Windows 和 Ubuntu 上进行了测试。

库代码在这里:https://github.com/TheLartians/StaticTypeInfo


C
Chris Uzdavinis

对于不同的东西,这里是类型的“到英语”转换,解构每个限定符、范围、参数等,递归地构建描述类型的字符串我认为“推导这个”提案将有助于减少许多专业化.无论如何,这是一个有趣的晨练,不管过度膨胀。 :)

struct X {
    using T = int *((*)[10]);
    T f(T, const unsigned long long * volatile * );
};

int main() {

    std::cout << describe<decltype(&X::f)>() << std::endl;
}

输出:

pointer to member function of class 1X taking (pointer to array[10]
of pointer to int, pointer to volatile pointer to const unsigned 
long long), and returning pointer to array[10] of pointer to int

这是代码:https://godbolt.org/z/7jKK4or43

注意:最新版本在我的 github 中:https://github.com/cuzdav/type_to_string

// Print types as strings, including functions, member 

#include <type_traits>
#include <typeinfo>
#include <string>
#include <utility>

namespace detail {

template <typename T> struct Describe;

template <typename T, class ClassT> 
struct Describe<T (ClassT::*)>  {
    static std::string describe();
};
template <typename RetT, typename... ArgsT> 
struct Describe<RetT(ArgsT...)>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...)>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...)&>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const &>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile &>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) & noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile &>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const & noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile & noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile & noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) &&>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const &&>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile &&>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) && noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile &&>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const && noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) volatile && noexcept>  {
    static std::string describe();
};
template <typename RetT, class ClassT, typename... ArgsT> 
struct Describe<RetT(ClassT::*)(ArgsT...) const volatile && noexcept>  {
    static std::string describe();
};

template <typename T>
std::string describe()
{
    using namespace std::string_literals;
    auto terminal = [&](char const * desc) {
        return desc + " "s + typeid(T).name();
    };
    if constexpr(std::is_const_v<T>) {
        return "const " + describe<std::remove_const_t<T>>();
    }
    else if constexpr(std::is_volatile_v<T>) {
        return "volatile " + describe<std::remove_volatile_t<T>>();
    }
    else if constexpr (std::is_same_v<bool, T>) {
        return "bool";
    }
    else if constexpr(std::is_same_v<char, T>) {
        return "char";
    }
    else if constexpr(std::is_same_v<signed char, T>) {
        return "signed char";
    }
    else if constexpr(std::is_same_v<unsigned char, T>) {
        return "unsigned char";
    }
    else if constexpr(std::is_unsigned_v<T>) {
        return "unsigned " + describe<std::make_signed_t<T>>();
    }
    else if constexpr(std::is_void_v<T>) {
        return "void";
    }
    else if constexpr(std::is_integral_v<T>) {
        if constexpr(std::is_same_v<short, T>) 
            return "short";
        else if constexpr(std::is_same_v<int, T>) 
            return "int";
        else if constexpr(std::is_same_v<long, T>) 
            return "long";
        else if constexpr(std::is_same_v<long long, T>) 
            return "long long";
    }
    else if constexpr(std::is_same_v<float, T>) {
        return "float";
    }
    else if constexpr(std::is_same_v<double, T>) {
        return "double";
    }
    else if constexpr(std::is_same_v<long double, T>) {
        return "long double";
    }
    else if constexpr(std::is_same_v<std::nullptr_t, T>) { 
        return "nullptr_t";
    }
    else if constexpr(std::is_class_v<T>) {
        return terminal("class");
    }
    else if constexpr(std::is_union_v<T>) {
        return terminal("union");
    }
    else if constexpr(std::is_enum_v<T>) {
        std::string result;
        if (!std::is_convertible_v<T, std::underlying_type_t<T>>) {
            result += "scoped ";
        }
        return result + terminal("enum");
    }  
    else if constexpr(std::is_pointer_v<T>) {
        return "pointer to " + describe<std::remove_pointer_t<T>>();
    }
    else if constexpr(std::is_lvalue_reference_v<T>) {
        return "lvalue-ref to " + describe<std::remove_reference_t<T>>();
    }
    else if constexpr(std::is_rvalue_reference_v<T>) {
        return "rvalue-ref to " + describe<std::remove_reference_t<T>>();
    }
    else if constexpr(std::is_bounded_array_v<T>) {
        return "array[" + std::to_string(std::extent_v<T>) + "] of " +
            describe<std::remove_extent_t<T>>();
    }
    else if constexpr(std::is_unbounded_array_v<T>) {
        return "array[] of " + describe<std::remove_extent_t<T>>();
    }
    else if constexpr(std::is_function_v<T>) {
        return Describe<T>::describe();
    }
    else if constexpr(std::is_member_object_pointer_v<T>) {
        return Describe<T>::describe();
    }
    else if constexpr(std::is_member_function_pointer_v<T>) {
        return Describe<T>::describe();
    }
}

template <typename RetT, typename... ArgsT> 
std::string Describe<RetT(ArgsT...)>::describe() {
    std::string result = "function taking (";
    ((result += detail::describe<ArgsT>(", ")), ...);
    return result + "), returning " + detail::describe<RetT>();
}

template <typename T, class ClassT> 
std::string Describe<T (ClassT::*)>::describe() {
    return "pointer to member of " + detail::describe<ClassT>() +
        " of type " + detail::describe<T>();
}

struct Comma {
    char const * sep = "";
    std::string operator()(std::string const& str) {
        return std::exchange(sep, ", ") + str;
    }
};
enum Qualifiers {NONE=0, CONST=1, VOLATILE=2, NOEXCEPT=4, LVREF=8, RVREF=16};

template <typename RetT, typename ClassT, typename... ArgsT>
std::string describeMemberPointer(Qualifiers q) {
    std::string result = "pointer to ";
    if (NONE != (q & CONST)) result += "const ";
    if (NONE != (q & VOLATILE)) result += "volatile ";
    if (NONE != (q & NOEXCEPT)) result += "noexcept ";
    if (NONE != (q & LVREF)) result += "lvalue-ref ";
    if (NONE != (q & RVREF)) result += "rvalue-ref ";
    result += "member function of " + detail::describe<ClassT>() + " taking (";
    Comma comma;
    ((result += comma(detail::describe<ArgsT>())), ...);
    return result + "), and returning " + detail::describe<RetT>();
}

template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...)>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(NONE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(CONST);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(VOLATILE | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(CONST | VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(CONST | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(CONST | VOLATILE | NOEXCEPT);
}

template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) &>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const &>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | CONST);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) & noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile &>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile & noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | VOLATILE | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile &>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | CONST | VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const & noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | CONST | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile & noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(LVREF | CONST | VOLATILE | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...)&&>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const &&>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | CONST);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) && noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile &&>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) volatile && noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | VOLATILE | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile &&>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | CONST | VOLATILE);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const && noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | CONST | NOEXCEPT);
}
template <typename RetT, class ClassT, typename... ArgsT> 
std::string Describe<RetT(ClassT::*)(ArgsT...) const volatile && noexcept>::describe() {
    return describeMemberPointer<RetT, ClassT, ArgsT...>(RVREF | CONST | VOLATILE | NOEXCEPT);
}

} // detail

///////////////////////////////////
// Main function
///////////////////////////////////
template <typename T>
std::string describe() {
    return detail::describe<T>();
}


///////////////////////////////////
// Sample code
///////////////////////////////////
#include <iostream>


struct X {
    using T = int *((*)[10]);
    T f(T, const unsigned long long * volatile * );
};

int main() {
    std::cout << describe<decltype(&X::f)>() << std::endl;
}


C
CourageousPotato

从此答案复制:https://stackoverflow.com/a/56766138/11502722

我能够让这个有点适用于 C++ static_assert()。这里的问题是 static_assert() 只接受字符串文字; constexpr string_view 将不起作用。您需要接受类型名周围的额外文本,但它有效:

template<typename T>
constexpr void assertIfTestFailed()
{
#ifdef __clang__
    static_assert(testFn<T>(), "Test failed on this used type: " __PRETTY_FUNCTION__);
#elif defined(__GNUC__)
    static_assert(testFn<T>(), "Test failed on this used type: " __PRETTY_FUNCTION__);
#elif defined(_MSC_VER)
    static_assert(testFn<T>(), "Test failed on this used type: " __FUNCSIG__);
#else
    static_assert(testFn<T>(), "Test failed on this used type (see surrounding logged error for details).");
#endif
    }
}

MSVC 输出:

error C2338: Test failed on this used type: void __cdecl assertIfTestFailed<class BadType>(void)
... continued trace of where the erroring code came from ...

J
Jonas

基于之前的一些答案,我提出了这个解决方案,它不会将 __PRETTY_FUNCTION__ 的结果存储在二进制文件中。它使用一个静态数组来保存类型名称的字符串表示。

它需要 C++23。

#include <iostream>
#include <string_view>
#include <array>

template <typename T>
constexpr auto type_name() {
    auto gen = [] <class R> () constexpr -> std::string_view  {
        return __PRETTY_FUNCTION__;
    };
    constexpr std::string_view search_type = "float";
    constexpr auto search_type_string = gen.template operator()<float>();
    constexpr auto prefix = search_type_string.find(search_type);
    constexpr auto suffix = search_type_string.size() - prefix - search_type.size();
    constexpr auto str = gen.template operator()<T>();
    constexpr int size = str.size() - prefix - suffix;
    constexpr auto static arr = [&]<std::size_t... I>(std::index_sequence<I...>) constexpr {
        return std::array<char, size>{str[prefix + I]...};
    } (std::make_index_sequence<size>{});

    return std::string_view(arr.data(), size);
}

H
Haseeb Mir

C++ 数据类型在编译时使用模板和运行时使用 TypeId 解析。

编译时解决方案。

template <std::size_t...Idxs>
constexpr auto substring_as_array(std::string_view str, std::index_sequence<Idxs...>)
{
  return std::array{str[Idxs]..., '\n'};
}

template <typename T>
constexpr auto type_name_array()
{
#if defined(__clang__)
  constexpr auto prefix   = std::string_view{"[T = "};
  constexpr auto suffix   = std::string_view{"]"};
  constexpr auto function = std::string_view{__PRETTY_FUNCTION__};
#elif defined(__GNUC__)
  constexpr auto prefix   = std::string_view{"with T = "};
  constexpr auto suffix   = std::string_view{"]"};
  constexpr auto function = std::string_view{__PRETTY_FUNCTION__};
#elif defined(_MSC_VER)
  constexpr auto prefix   = std::string_view{"type_name_array<"};
  constexpr auto suffix   = std::string_view{">(void)"};
  constexpr auto function = std::string_view{__FUNCSIG__};
#else
# error Unsupported compiler
#endif

  constexpr auto start = function.find(prefix) + prefix.size();
  constexpr auto end = function.rfind(suffix);

  static_assert(start < end);

  constexpr auto name = function.substr(start, (end - start));
  return substring_as_array(name, std::make_index_sequence<name.size()>{});
}

template <typename T>
struct type_name_holder {
  static inline constexpr auto value = type_name_array<T>();
};

template <typename T>
constexpr auto type_name() -> std::string_view
{
  constexpr auto& value = type_name_holder<T>::value;
  return std::string_view{value.data(), value.size()};
}

运行时解决方案。

template <typename T>
void PrintDataType(T type)
{
    auto name = typeid(type).name();
    string cmd_str = "echo '" + string(name) + "' | c++filt -t";
    system(cmd_str.c_str());
}

主要代码

#include <iostream>
#include <map>
#include <string>
#include <typeinfo>
#include <string_view>
#include <array>   // std::array
#include <utility> // std::index_sequence
using std::string;

int main() { //动态分辨率。 std::map iMap;打印数据类型(iMap); //编译类型解析。 std::cout << type_name>() << std::endl;返回0; }

Code Snippet


规则要求答案是独立的。您可以将链接中的代码添加到答案本身吗?
@HolyBlackCat 好的,现在我添加了显示代码运行的 Main 方法。
那并非我的本意。我的意思是从链接中添加所有代码。
80行不算多。关键是,即使/如果链接失效,您的答案也应该仍然有效。以下是更多信息:Are answers that just contain links elsewhere really "good answers"?
好的,我也会在这里发布答案,谢谢。