How*_*ant 456

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()
Run Code Online (Sandbox Code Playgroud)

扔掉cv-qualifiers,引用和左值/右值.例如:

const int ci = 0;
std::cout << typeid(ci).name() << '\n';
Run Code Online (Sandbox Code Playgroud)

对我来说输出:

i
Run Code Online (Sandbox Code Playgroud)

我猜测MSVC输出:

int
Run Code Online (Sandbox Code Playgroud)

const它已经消失了.这不是QOI(实施质量)问题.该标准规定了这种行为.

我在下面推荐的是:

template <typename T> std::string type_name();
Run Code Online (Sandbox Code Playgroud)

将使用这样:

const int ci = 0;
std::cout << type_name<decltype(ci)>() << '\n';
Run Code Online (Sandbox Code Playgroud)

对我来说输出:

int const
Run Code Online (Sandbox Code Playgroud)

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

C++ 11解决方案

我正在使用ipapadop__cxa_demangle推荐的非MSVC平台,他对demangle类型的回答.但在MSVC上,我相信可以解密名称(未经测试).这个核心包含一些简单的测试,可以检测,恢复和报告cv限定符以及对输入类型的引用.typeid

#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;
}
Run Code Online (Sandbox Code Playgroud)

结果

有了这个解决方案,我可以这样做:

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';
}
Run Code Online (Sandbox Code Playgroud)

输出是:

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
Run Code Online (Sandbox Code Playgroud)

注(例如)之间的差值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
Run Code Online (Sandbox Code Playgroud)

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

C++ 14更新

就在你认为你已经找到了解决问题的解决方案的时候,总会有人突然出现并向你展示一个更好的方法.:-)

来自Jamboree的答案显示了如何在编译时在C++ 14中获取类型名称.这是一个很好的解决方案,原因如下:

  1. 它是在编译时!
  2. 你得到编译器本身来完成工作而不是库(甚至是std :: lib).这意味着最新语言功能(如lambdas)的结果更准确.

Jamboree的 回答并不能完全解决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
}
Run Code Online (Sandbox Code Playgroud)

constexpr如果您仍然停留在古老的C++ 11中,此代码将自动退回.如果你用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
}
Run Code Online (Sandbox Code Playgroud)

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

  • @AngelusMortis:因为英语与C++代码相比模糊/含糊不清,我建议您将其复制/粘贴到您感兴趣的特定类型的测试用例中,并使用您感兴趣的特定编译器,并回复更多如果结果令人惊讶和/或不满意,请详细说明. (5认同)
  • VS 14 CTP打印出正确的类型,我只需添加一个`#include <iostream>`行. (4认同)
  • 为什么模板<typename T> std :: string type_name()?你为什么不传递一个类型作为参数? (3认同)
  • 我相信我的理由是有时我*只*有一个类型(例如推导出的模板参数),我不想人为地构造其中一个来获得类型(虽然这些天`declval`会做工作). (2认同)
  • @HowardHinnant您可以使用`std :: string_view`而不是`static_string`吗? (2认同)
  • 您能使C ++ 17不使用幻数,而是让读者理解那些偏移量的东西吗? (2认同)

Kon*_*lph 224

尝试:

#include <typeinfo>

// …
std::cout << typeid(a).name() << '\n';
Run Code Online (Sandbox Code Playgroud)

您可能必须在编译器选项中激活RTTI才能使其生效.另外,这个输出取决于编译器.它可能是原始类型名称或名称重整符号或介于两者之间的任何内容.

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

Nic*_*ckV 78

如果你只想要编译时间信息(例如用于调试),那么非常难看

auto testVar = std::make_tuple(1, 1.0, "abc");
decltype(testVar)::foo= 1;
Run Code Online (Sandbox Code Playgroud)

返回:

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*>'
Run Code Online (Sandbox Code Playgroud)

  • 感谢您将错误用作C++功能! (17认同)
  • 只有 c++ 可以使这变得如此困难(在编译时打印自动变量类型)。只有 C++。 (6认同)
  • @KarlP很公平,这有点令人费解,这也有效:)```auto testVar = std :: make_tuple(1,1.0,"abc"); decltype(testVar):: foo = 1;``` (3认同)
  • 这种技术也在 Effective Modern C++ 的“Item 4: Know how to view deduced types”中描述 (2认同)

mde*_*dec 53

别忘了包括 <typeinfo>

我相信你所指的是运行时类型识别.你可以做到这一点.

#include <iostream>
#include <typeinfo>

using namespace std;

int main() {
  int i;
  cout << typeid(i).name();
  return 0;
}
Run Code Online (Sandbox Code Playgroud)


pae*_*bal 22

请注意,C++的RTTI功能生成的名称不可移植.例如,班级

MyNamespace::CMyContainer<int, test_MyNamespace::CMyObject>
Run Code Online (Sandbox Code Playgroud)

将具有以下名称:

// MSVC 2003:
class MyNamespace::CMyContainer[int,class test_MyNamespace::CMyObject]
// G++ 4.2:
N8MyNamespace8CMyContainerIiN13test_MyNamespace9CMyObjectEEE
Run Code Online (Sandbox Code Playgroud)

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


Nic*_*ick 18

您可以使用模板.

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

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

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

康桓瑋*_*康桓瑋 17

根据霍华德的解决方案,如果您不想要幻数,我认为这是代表并且看起来直观的好方法:

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;
}   
Run Code Online (Sandbox Code Playgroud)

  • 这是对过去几个 C++ 版本的努力的一次伟大的升华,变成了简短而甜蜜的东西。+1。 (6认同)
  • 这也是我最喜欢的! (3认同)
  • 这里我使用一个类似的函数,自动检测后缀/前缀:/sf/ask/73881671/​​-template/59522794#59522794 (2认同)

ipa*_*dop 15

如上所述,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;
Run Code Online (Sandbox Code Playgroud)

}


Val*_*Val 11

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;
}
Run Code Online (Sandbox Code Playgroud)

输出

海湾合作委员会 10.2

test
const int *&
unsigned int
const int
const int *
const int *&
typeName::probe_type
Run Code Online (Sandbox Code Playgroud)

叮当 11.0.0

test
const int *&
unsigned int
const int
const int *
const int *&
typeName::probe_type
Run Code Online (Sandbox Code Playgroud)

VS 2019 版本 16.7.6:

class test
const int*&
unsigned int
const int
const int*
const int*&
class typeName::probe_type
Run Code Online (Sandbox Code Playgroud)


Gre*_*ill 10

你可以使用traits类.就像是:

#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;
}
Run Code Online (Sandbox Code Playgroud)

DECLARE_TYPE_NAME定义的存在是为了让您的生活更容易在这个声明traits类为所有你期望需要的类型.

这可能比涉及的解决方案更有用,typeid因为您可以控制输出.例如,在我的编译器上使用typeidfor long long给出"x".


小智 9

在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;
}
Run Code Online (Sandbox Code Playgroud)


ein*_*ica 9

另一个对@??? 的回答(最初)的看法,对前缀和后缀的细节做出较少的假设,并受到@Val 回答的启发- 但不污染全局命名空间;没有任何条件;希望更容易阅读。

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

不幸的是,类型名称包含在描述函数的文本中,这在编译器之间是不同的。例如,对于 GCC,template <typename T> int foo()with 类型的签名double是: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);
}
Run Code Online (Sandbox Code Playgroud)

看看吧GodBolt。这也应该与 MSVC 一起使用。


Ala*_*lan 8

您还可以使用带有选项 -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;
}
Run Code Online (Sandbox Code Playgroud)

仅在 linux 上测试。


Mil*_* Lu 7

正如 Scott Meyers 在《Effective Modern C++》中所解释的那样,

\n
\n

std::type_info::name不保证调用会返回任何有意义的内容。

\n
\n

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

\n
template<typename T>\nclass TD;\n\nint main(){\n    const int theAnswer = 32;\n    auto x = theAnswer;\n    auto y = &theAnswer;\n    TD<decltype(x)> xType;\n    TD<decltype(y)> yType;\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
test4.cpp:10:21: error: aggregate \xe2\x80\x98TD<int> xType\xe2\x80\x99 has incomplete type and cannot be defined TD<decltype(x)> xType;\n\ntest4.cpp:11:21: error: aggregate \xe2\x80\x98TD<const int *> yType\xe2\x80\x99 has incomplete type and cannot be defined TD<decltype(y)> yType;\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n


Jam*_*kin 5

涉及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";
};
Run Code Online (Sandbox Code Playgroud)

请注意,如果将声明包装在宏中,由于逗号,您将无法为使用多个参数(例如std :: map)的模板类型声明名称.

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

template <typename T>
const char* get_type_as_string(const T&)
{
    return type_as_string<T>::value;
}
Run Code Online (Sandbox Code Playgroud)

  • static const char*value ="Wibble"; 你做不到那个伙伴:) (2认同)

Jah*_*hid 5

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

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*"; }
Run Code Online (Sandbox Code Playgroud)

  • (i) 它不适用于其他类型(即根本不是通用的);(ii) 无用的代码膨胀;(iii) 同样可以(正确地)用 `typeid` 或 `decltype` 完成。 (2认同)
  • 你是对的,但它涵盖了所有基本类型......这就是我现在需要的...... (2认同)
  • 你能告诉我,你会如何用 decltype 做到这一点, (2认同)

Jah*_*hid 5

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

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;}
Run Code Online (Sandbox Code Playgroud)

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;}
Run Code Online (Sandbox Code Playgroud)

输出:

int
String
MyClass
Run Code Online (Sandbox Code Playgroud)


Hol*_*Cat 5

当我挑战时,我决定测试独立于平台(希望如此)的模板技巧能走多远。

名称在编译时完全组装。(这意味着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)`.
}
Run Code Online (Sandbox Code Playgroud)

代码:

#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);};
Run Code Online (Sandbox Code Playgroud)