例如:
int a = 12;
cout << typeof(a) << endl;
预期的输出:
int
例如:
int a = 12;
cout << typeof(a) << endl;
预期的输出:
int
当前回答
根据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;
}
演示。
其他回答
在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;
}
对于那些还在访问的人,我最近也遇到了同样的问题,并决定根据这篇文章的答案编写一个小型库。它提供了constexpr类型名称和类型索引,并且在Mac, Windows和Ubuntu上进行了测试。
库代码在这里:https://github.com/TheLartians/StaticTypeInfo
对于一些不同的东西,这里有一个类型的“To English”转换,解构每个限定符、范围、参数等等,递归地构建描述类型的字符串,我认为“演绎这个”建议将有助于减少许多特殊化。无论如何,这是一个有趣的晨练,尽管过度膨胀。:)
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;
}
另一个@康桓瑋的答案(最初),对前缀和后缀细节做了较少的假设,并受到@ val的答案的启发-但没有污染全局名称空间;无条件的:没有任何条件的;希望更容易阅读。
流行的编译器提供了带有当前函数签名的宏。现在,函数是可模板化的;因此签名包含模板参数。因此,基本的方法是:给定一个类型,在函数中使用该类型作为模板参数。
不幸的是,类型名称被包装在描述函数的文本中,这在不同的编译器中是不同的。例如,在GCC中,template <typename T> int foo()具有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);
}
可以在GodBolt上看到。这应该与MSVC工作以及。
如前所述,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;
}