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

时间:2008-09-17 10:34:44

标签: c++ variables c++11 typeof

例如:

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

预期产出:

int

22 个答案:

答案 0 :(得分:405)

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-qualifiers,reference和lvalue / rvalue-ness。例如:

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推荐的非MSVC平台中使用__cxa_demangle来解决demangle类型。但是在MSVC上,我相信typeid能够解密名称(未经测试)。这个核心包含一些简单的测试,可以检测,恢复和报告cv限定符以及对输入类型的引用。

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

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

来自This answer

Jamboree显示了如何在编译时在C ++ 14中获取类型名称。由于以下几个原因,这是一个出色的解决方案:

  1. 这是在编译时!
  2. 你得到编译器本身来完成工作而不是库(甚至是std :: lib)。这意味着最新语言功能(如lambdas)的结果更准确。
  3. Jamboree's answer并不能完全解决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 ++ 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
    }
    

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

答案 1 :(得分:221)

尝试:

#include <typeinfo>

// …
std::cout << typeid(a).name() << '\n';

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

答案 2 :(得分:64)

非常难看,但如果你只想要编译时间信息(例如调试),那就可以了。

auto testVar = std::make_tuple(1, 1.0, "abc");
decltype(testVar)::foo = 1;

返回:

Compilation finished with errors:
source.cpp: In function 'int main()':
source.cpp:5:19: error: 'dummy_error' is not a member of 'std::tuple<int, double, const char*>'

答案 3 :(得分:53)

不要忘记包含<typeinfo>

我相信你所指的是运行时类型识别。你可以通过这样做来实现上述目标。

#include <iostream>
#include <typeinfo>

using namespace std;

int main() {
  int i;
  cout << typeid(i).name();
  return 0;
}

答案 4 :(得分:22)

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

MyNamespace::CMyContainer<int, test_MyNamespace::CMyObject>

将具有以下名称:

// MSVC 2003:
class MyNamespace::CMyContainer[int,class test_MyNamespace::CMyObject]
// G++ 4.2:
N8MyNamespace8CMyContainerIiN13test_MyNamespace9CMyObjectEEE

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

答案 5 :(得分: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"; }

在上面的例子中,当类型不匹配时,它将打印“unknown”。

答案 6 :(得分:14)

如前所述,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;

}

答案 7 :(得分: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;
}

DECLARE_TYPE_NAME定义的存在是为了让您的生活更轻松,为您期望的所有类型声明此特征类。

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

答案 8 :(得分:6)

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

答案 9 :(得分: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";
};

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

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

template <typename T>
const char* get_type_as_string(const T&)
{
    return type_as_string<T>::value;
}

答案 10 :(得分:4)

您也可以使用带有选项-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;
}

仅在linux上测试。

答案 11 :(得分:4)

比我之前没有函数重载的更通用的解决方案:

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

这里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;}

输出:

int
String
MyClass

答案 12 :(得分:4)

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

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*"; }

答案 13 :(得分:3)

当我挑战时,我决定用独立于平台(希望)的模板技巧测试一下可以走多远。

名称在编译时完全汇编。 (这意味着date: "{"date":"2017-08-01","titles":[{"title":"title test","url":"xxx/xxx","category":"category test"}]}" headers: Accept: "application/json, text/plain, */*" Content-Type: "application/json;charset=utf-8" 无法使用,因此您必须明确提供非复合类型的名称。否则将显示占位符。)

使用示例:

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)`.
}

答案 14 :(得分:3)

采用另一种@康桓瑋's answer(原本为)的方法,对前缀和后缀的特定性进行较少的假设,并受@Val's answer的启发-但不污染全局名称空间;没有任何条件;并且希望更容易阅读。

流行的编译器为宏提供了当前函数的签名。现在,功能是可模板化的。因此签名包含模板参数。因此,基本方法是:给定类型,将其作为模板参数放在函数中。

不幸的是,类型名称被包裹在描述函数的文本中,这在编译器之间是不同的。例如,对于GCC,类型为template <typename T> int foo()的{​​{1}}的签名为:double

那么,如何摆脱包装文本? @HowardHinnant的解决方案是最短,最“直接”的解决方案:只需使用每个编译器的幻数即可删除前缀和后缀。但是显然,这非常脆弱。没有人喜欢他们代码中的幻数。相反,您将获得具有已知名称的类型的宏值,然后可以确定构成包装的前缀和后缀。

int foo() [T = double]

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

答案 15 :(得分:2)

Howard Hinnant使用幻数提取类型名称。 康桓瑋建议使用字符串前缀和后缀。但是前缀/后缀不断变化。 使用“ probe_type”,type_name自动计算“ probe_type”的前缀和后缀大小,以提取类型名称:

#include <iostream>
#include <string_view>

using namespace std;

class probe_type;

template <typename T>
constexpr string_view type_name() {
  string_view probe_type_name("class probe_type");
  const string_view class_specifier("class");

  string_view name;
#ifdef __clang__
  name = __PRETTY_FUNCTION__;
  probe_type_name.remove_prefix(class_specifier.length());
#elif defined(__GNUC__)
  name = __PRETTY_FUNCTION__;
  probe_type_name.remove_prefix(class_specifier.length());
#elif defined(_MSC_VER)
  name = __FUNCSIG__;
#endif

  if (name.find(probe_type_name) != string_view::npos)
    return name;

  const string_view probe_type_raw_name = type_name<probe_type>();

  const size_t prefix_size = probe_type_raw_name.find(probe_type_name);

  name.remove_prefix(prefix_size);
  name.remove_suffix(probe_type_raw_name.length() - prefix_size - probe_type_name.length());

  return name;
}

class test;

int main() {
  cout << type_name<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;
}

输出

gcc 10.0.0 20190919 Wandbox:

 test
 const int *&
 unsigned int
 const int
 const int *
 const int *&
 constexpr std::string_view type_name() [with T = probe_type; std::string_view = std::basic_string_view<char>]

c 10.0.0 Wandbox:

 test
 const int *&
 unsigned int
 const int
 const int *
 const int *&
 std::__1::string_view type_name() [T = probe_type]

VS 2019版本16.3.3:

class test
const int*&
unsigned int
const int
const int*
const int*&
class std::basic_string_view<char,struct std::char_traits<char> > __cdecl type_name<class probe_type>(void)

答案 16 :(得分:1)

skip()

输出:

#include <iostream>
#include <typeinfo>
using namespace std;
#define show_type_name(_t) \
    system(("echo " + string(typeid(_t).name()) + " | c++filt -t").c_str())

int main() {
    auto a = {"one", "two", "three"};
    cout << "Type of a: " << typeid(a).name() << endl;
    cout << "Real type of a:\n";
    show_type_name(a);
    for (auto s : a) {
        if (string(s) == "one") {
            cout << "Type of s: " << typeid(s).name() << endl;
            cout << "Real type of s:\n";
            show_type_name(s);
        }
        cout << s << endl;
    }

    int i = 5;
    cout << "Type of i: " << typeid(i).name() << endl;
    cout << "Real type of i:\n";
    show_type_name(i);
    return 0;
}

答案 17 :(得分:1)

对于仍在访问的任何人,我最近都遇到了同样的问题,因此决定根据本文的答案编写一个小型图书馆。它提供constexpr类型名称和类型索引,并且已在Mac,Windows和Ubuntu上进行了测试。

库代码在这里:https://github.com/TheLartians/StaticTypeInfo

答案 18 :(得分:0)

如Scott Meyers在《有效的现代C ++》中所述,

  

std::type_info::name的调用不能保证返回任何合理的结果。

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

template<typename T>
class TD;

int main(){
    const int theAnswer = 32;
    auto x = theAnswer;
    auto y = &theAnswer;
    TD<decltype(x)> xType;
    TD<decltype(y)> yType;
    return 0;
}

根据编译器的不同,结果将是这样

test4.cpp:10:21: error: aggregate ‘TD<int> xType’ has incomplete type and cannot be defined TD<decltype(x)> xType;

test4.cpp:11:21: error: aggregate ‘TD<const int *> yType’ has incomplete type and cannot be defined TD<decltype(y)> yType;

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

答案 19 :(得分:0)

根据霍华德的解决方案,如果您不希望使用幻数,我认为这是一种很好的表示方式,而且看起来很简单。 Here is the godbolt static testing.

template <typename T>
constexpr auto
type_name()
{
    using namespace std::string_view_literals;
#ifdef __clang__
    constexpr auto prefix = "auto type_name() [T = "sv;
    constexpr auto suffix = "]"sv;
    std::string_view name = __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
    constexpr auto prefix = "constexpr auto type_name() [with T = "sv;
    constexpr auto suffix = "]"sv;
    std::string_view name = __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
    constexpr auto prefix = "auto __cdecl type_name<"sv;
    constexpr auto suffix = ">(void)"sv;
    std::string_view name = __FUNCSIG__;
#endif
    name.remove_prefix(prefix.size());
    name.remove_suffix(suffix.size());
    return name;
}

答案 20 :(得分:0)

从以下答案中复制:https://stackoverflow.com/a/56766138/11502722

我能够为C ++ static_assert()使用此有点。这里的缺点是static_assert()仅接受字符串文字; constexpr string_view不起作用。您将需要在类型名周围接受额外的文本,但是它可以工作:

template<typename T>
constexpr void assertIfTestFailed()
{
#ifdef __clang__
    static_assert(testFn<T>(), "Test failed on this used type: " __PRETTY_FUNCTION__);
#elif defined(__GNUC__)
    static_assert(testFn<T>(), "Test failed on this used type: " __PRETTY_FUNCTION__);
#elif defined(_MSC_VER)
    static_assert(testFn<T>(), "Test failed on this used type: " __FUNCSIG__);
#else
    static_assert(testFn<T>(), "Test failed on this used type (see surrounding logged error for details).");
#endif
    }
}

MSVC输出:

error C2338: Test failed on this used type: void __cdecl assertIfTestFailed<class BadType>(void)
... continued trace of where the erroring code came from ...

答案 21 :(得分:0)

对于不同的东西,这里是类型的“到英语”转换,解构每个限定符、范围、参数等,递归构建描述类型的字符串我认为“推导出这个”提案将有助于减少许多的专业。无论如何,这是一次有趣的晨练,不​​管是否过度膨胀。 :)

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

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