小编典典

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

all

例如:

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

预期输出:

int

阅读 160

收藏
2022-03-09

共1个答案

小编典典

C11 更新了一个非常老的问题:在 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
上,我相信可以解开名称(未经测试)。这个核心包含一些简单的测试,检测、恢复和报告 cv-qualifiers
和对输入类型的引用。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;
}

结果

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

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-qualifiers 或引用,则输出将是:

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 更新

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

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

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

并没有完全说明 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
}

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

C++17 更新

在下面的评论中,指出新的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 的常量。

更新:

请务必查看下面的重写,它消除了我最新公式中不可读的幻数。
如果你不喜欢幻数,我认为这是一种很好的表示方式,而且看起来很直观:

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

演示。

2022-03-09