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

新手上路,请多包涵

例如:

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

预期输出:

 int

原文由 Jorge Ferreira 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.2k
2 个回答

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 解决方案

我正在使用 __cxa_demangle 用于非 MSVC 平台,正如 ipapadop 在他对 demangle 类型的回答中所推荐的那样。但在 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这个答案 显示了如何在编译时获取 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
}

如果您仍然停留在古老的 C++11 中,此代码将在 constexpr 上自动退避。如果你用 C++9803 在洞壁上作画,那么 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 的常量。

更新:

请务必查看 下面的重写, 它消除了我最新公式中不可读的幻数。

原文由 Howard Hinnant 发布,翻译遵循 CC BY-SA 4.0 许可协议

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()
{
    //Dynamic resolution.
    std::map<int, int> iMap;
    PrintDataType(iMap);

    //Compile type resolution.
    std::cout << type_name<std::list<int>>() << std::endl;

    return 0;
}

代码片段

原文由 Haseeb Mir 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题