是否可以在标准 C++ 中打印变量的类型?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/81870/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is it possible to print a variable's type in standard C++?
提问by Jorge Ferreira
For example:
例如:
int a = 12;
cout << typeof(a) << endl;
Expected output:
预期输出:
int
回答by Howard Hinnant
C++11 update to a very old question: Print variable type in C++.
C++11 更新了一个非常古老的问题:在 C++ 中打印变量类型。
The accepted (and good) answer is to use typeid(a).name()
, where a
is a variable name.
公认的(好的)答案是使用typeid(a).name()
, wherea
是变量名。
Now in C++11 we have decltype(x)
, which can turn an expression into a type. And decltype()
comes with its own set of very interesting rules. For example decltype(a)
and decltype((a))
will generally be different types (and for good and understandable reasons once those reasons are exposed).
现在在 C++11 中,我们有decltype(x)
,它可以将表达式转换为类型。并decltype()
带有自己的一套非常有趣的规则。例如,decltype(a)
并且decltype((a))
通常会是不同的类型(并且一旦这些原因暴露出来,就会有很好的和可以理解的原因)。
Will our trusty typeid(a).name()
help us explore this brave new world?
我们可信赖的人会typeid(a).name()
帮助我们探索这个美丽的新世界吗?
No.
不。
But the tool that will is not that complicated. And it is that tool which I am using as an answer to this question. I will compare and contrast this new tool to typeid(a).name()
. And this new tool is actually built on top of typeid(a).name()
.
但是这个工具并没有那么复杂。这就是我用来回答这个问题的工具。我将把这个新工具与typeid(a).name()
. 而这个新工具实际上是建立在typeid(a).name()
.
The fundamental issue:
根本问题:
typeid(a).name()
throws away cv-qualifiers, references, and lvalue/rvalue-ness. For example:
丢弃 cv 限定符、引用和左值/右值。例如:
const int ci = 0;
std::cout << typeid(ci).name() << '\n';
For me outputs:
对我来说输出:
i
and I'm guessing on MSVC outputs:
我在猜测 MSVC 输出:
int
I.e. the const
is gone. This is not a QOI (Quality Of Implementation) issue. The standard mandates this behavior.
即const
消失了。这不是 QOI(实施质量)问题。该标准规定了这种行为。
What I'm recommending below is:
我在下面推荐的是:
template <typename T> std::string type_name();
which would be used like this:
这将像这样使用:
const int ci = 0;
std::cout << type_name<decltype(ci)>() << '\n';
and for me outputs:
对我来说输出:
int const
<disclaimer>
I have not tested this on MSVC. </disclaimer>
But I welcome feedback from those who do.
<disclaimer>
我没有在 MSVC 上测试过这个。</disclaimer>
但我欢迎那些这样做的人的反馈。
The C++11 Solution
C++11 解决方案
I am using __cxa_demangle
for non-MSVC platforms as recommend by ipapadopin his answer to demangle types. But on MSVC I'm trusting typeid
to demangle names (untested). And this core is wrapped around some simple testing that detects, restores and reports cv-qualifiers and references to the input type.
我正在使用ipapadop在他对 demangle 类型的回答中__cxa_demangle
推荐的非 MSVC 平台。但是在 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;
}
The Results
结果
With this solution I can do this:
有了这个解决方案,我可以做到这一点:
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';
}
and the output is:
输出是:
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
Note (for example) the difference between decltype(i)
and decltype((i))
. The former is the type of the declarationof i
. The latter is the "type" of the expressioni
. (expressions never have reference type, but as a convention decltype
represents lvalue expressions with lvalue references).
注(例如)之间的差值decltype(i)
和decltype((i))
。前者是类型声明的i
。后者是表达式的“类型” i
。(表达式从不具有引用类型,但作为约定decltype
表示具有左值引用的左值表达式)。
Thus this tool is an excellent vehicle just to learn about decltype
, in addition to exploring and debugging your own code.
因此decltype
,除了探索和调试您自己的代码之外,这个工具还是一个很好的学习工具。
In contrast, if I were to build this just on typeid(a).name()
, without adding back lost cv-qualifiers or references, the output would be:
相比之下,如果我只是在 上构建它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
I.e. Every reference and cv-qualifier is stripped off.
即每个引用和 cv 限定符都被剥离了。
C++14 Update
C++14 更新
Just when you think you've got a solution to a problem nailed, someone always comes out of nowhere and shows you a much better way. :-)
就在您认为自己已经找到了解决问题的方法时,总会有人突然冒出来向您展示更好的方法。:-)
This answerfrom Jamboreeshows how to get the type name in C++14 at compile time. It is a brilliant solution for a couple reasons:
这个答案从大露营展示了如何获得C ++ 14的类型名称在编译时。出于以下几个原因,这是一个出色的解决方案:
- It's at compile time!
- You get the compiler itself to do the job instead of a library (even a std::lib). This means more accurate results for the latest language features (like lambdas).
- 这是在编译时!
- 您让编译器本身来完成这项工作,而不是使用库(甚至是 std::lib)。这意味着最新语言功能(如 lambdas)的结果更准确。
Jamboree'sanswerdoesn't quite lay everything out for VS, and I'm tweaking his code a little bit. But since this answer gets a lot of views, take some time to go over there and upvote his answer, without which, this update would never have happened.
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
}
This code will auto-backoff on the constexpr
if you're still stuck in ancient C++11. And if you're painting on the cave wall with C++98/03, the noexcept
is sacrificed as well.
constexpr
如果您仍然停留在古老的 C++11 中,此代码将自动退避。如果你用 C++98/03 在洞壁上作画,那么它也会noexcept
被牺牲。
C++17 Update
C++17 更新
In the comments below Lybertapoints out that the new std::string_view
can replace static_string
:
在下面的评论中,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
}
I've updated the constants for VS thanks to the very nice detective work by Jive Dadson in the comments below.
由于 Jive Dadson 在下面的评论中所做的非常出色的侦探工作,我已经更新了 VS 的常量。
Update:
更新:
Be sure to check out this rewrite belowwhich eliminates the unreadable magic numbers in my latest formulation.
请务必查看下面的重写,它消除了我最新公式中不可读的幻数。
回答by Konrad Rudolph
Try:
尝试:
#include <typeinfo>
// …
std::cout << typeid(a).name() << '\n';
You might have to activate RTTI in your compiler options for this to work. Additionally, the output of this depends on the compiler. It might be a raw type name or a name mangling symbol or anything in between.
您可能必须在编译器选项中激活 RTTI 才能使其工作。此外,它的输出取决于编译器。它可能是原始类型名称或名称修改符号或介于两者之间的任何内容。
回答by NickV
Very ugly but does the trick if you only want compile time info (e.g. for debugging):
非常难看,但如果您只想要编译时间信息(例如用于调试),就可以解决问题:
auto testVar = std::make_tuple(1, 1.0, "abc");
decltype(testVar)::foo= 1;
Returns:
返回:
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*>'
回答by mdec
Don't forget to include <typeinfo>
不要忘记包括 <typeinfo>
I believe what you are referring to is runtime type identification. You can achieve the above by doing .
我相信您所指的是运行时类型识别。您可以通过执行 .
#include <iostream>
#include <typeinfo>
using namespace std;
int main() {
int i;
cout << typeid(i).name();
return 0;
}
回答by u11638718
According to Howard's solution, if you don't want the magic number, I think this is a good way to represent and looks intuitive:
根据Howard的解决方案,如果您不想要幻数,我认为这是一种很好的表示方式并且看起来很直观:
#include <string_view>
template <typename T>
constexpr std::string_view
type_name()
{
std::string_view name, prefix, suffix;
#ifdef __clang__
name = __PRETTY_FUNCTION__;
prefix = "std::string_view type_name() [T = ";
suffix = "]";
#elif defined(__GNUC__)
name = __PRETTY_FUNCTION__;
prefix = "constexpr std::string_view type_name() [with T = ";
suffix = "; std::string_view = std::basic_string_view<char>]";
#elif defined(_MSC_VER)
name = __FUNCSIG__;
prefix = "class std::basic_string_view<char,struct std::char_traits<char> > __cdecl type_name<";
suffix = ">(void)";
#endif
name.remove_prefix(prefix.size());
name.remove_suffix(suffix.size());
return name;
}
回答by paercebal
Note that the names generated by the RTTI feature of C++ is notportable. For example, the class
请注意,由 C++ 的 RTTI 功能生成的名称不可移植。例如,类
MyNamespace::CMyContainer<int, test_MyNamespace::CMyObject>
will have the following names:
将具有以下名称:
// MSVC 2003:
class MyNamespace::CMyContainer[int,class test_MyNamespace::CMyObject]
// G++ 4.2:
N8MyNamespace8CMyContainerIiN13test_MyNamespace9CMyObjectEEE
So you can't use this information for serialization. But still, the typeid(a).name() property can still be used for log/debug purposes
因此,您不能将此信息用于序列化。但是, typeid(a).name() 属性仍然可以用于日志/调试目的
回答by Nick
You can use templates.
您可以使用模板。
template <typename T> const char* typeof(T&) { return "unknown"; } // default
template<> const char* typeof(int&) { return "int"; }
template<> const char* typeof(float&) { return "float"; }
In the example above, when the type is not matched it will print "unknown".
在上面的例子中,当类型不匹配时,它会打印“unknown”。
回答by ipapadop
As mentioned, typeid().name()
may return a mangled name. In GCC (and some other compilers) you can work around it with the following code:
如前所述,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;
}
}
回答by Greg Hewgill
You could use a traits class for this. Something like:
您可以为此使用特征类。就像是:
#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;
}
The DECLARE_TYPE_NAME
define exists to make your life easier in declaring this traits class for all the types you expect to need.
在DECLARE_TYPE_NAME
限定的存在是为了让您的生活更容易在这个声明traits类为所有你希望需要的类型。
This might be more useful than the solutions involving typeid
because you get to control the output. For example, using typeid
for long long
on my compiler gives "x".
这可能比涉及的解决方案更有用,typeid
因为您可以控制输出。例如,在我的编译器上使用typeid
forlong long
会给出“x”。
回答by abodeofcode
In C++11, we have decltype. There is no way in standard c++ to display exact type of variable declared using decltype. We can use boost typeindex i.e type_id_with_cvr
(cvr stands for const, volatile, reference) to print type like below.
在 C++11 中,我们有 decltype。在标准 C++ 中没有办法显示使用 decltype 声明的变量的确切类型。我们可以使用 boost typeindex ie 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;
}