是否有可能在标准C ++中打印variables的types?

例如:

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

预期产出:

 int 

C ++ 11更新为一个非常古老的问题:在C ++中打印variablestypes。

被接受的(和好的)答案是使用typeid(a).name() ,其中a是一个variables名。

现在在C ++ 11中,我们有了decltype(x) ,它可以把一个expression式转换成一个types。 decltype()带有一套非常有趣的规则。 例如, decltype(a)decltype((a))通常是不同的types(并且一旦这些原因暴露出来就是出于良好和可理解的原因)。

我们可信的typeid(a).name()帮助我们探索这个勇敢的新世界吗?

没有。

但是这个工具不会那么复杂。 这是我正在使用的工具,作为这个问题的答案。 我将比较和对比这个新的工具typeid(a).name() 。 而这个新的工具实际上build立在typeid(a).name()之上。

根本问题:

 typeid(a).name() 

抛出cv-qualifiers,引用和左值/右值。 例如:

 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上testing过这个。 但是我欢迎那些做的人的反馈。

C ++ 11解决scheme

对于非MSVC平台,我正在使用__cxa_demangle ,正如ipapadop在demangletypes的答案中所build议的那样 。 但是在MSVC上,我相信typeid可以取消名字(未经testing)。 而这个核心是围绕一些简单的testing,检测,恢复和报告inputtypes的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; } 

结果

有了这个解决scheme,我可以这样做:

 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声明的types。 后者是expression式 i的“types”。 (expression式从来没有引用types,但作为约定, decltype表示左值引用的左值expression式)。

因此,除了探索和debugging你自己的代码之外,这个工具是学习decltype的绝佳工具。

相反,如果我只是在typeid(a).name()上构build它,而不添加丢失的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-qualifier被剥离。

C ++ 14更新

就在你认为你已经解决了一个钉子问题的解决scheme的时候,总是有人冒出来,向你展示一个更好的方法。 🙂

Jamboree的 这个答案显示了如何在编译时在C ++ 14中获取types名称。 这是一个辉煌的解决scheme,由于几个原因:

  1. 这是在编译时间!
  2. 你得到编译器本身而不是一个库(甚至是一个std :: lib)的工作。 这意味着对于最新的语言function(如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 ++ noexcept在洞壁上绘画, noexcept被牺牲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 } 

我已经更新了VS的常量,这要感谢Jive Dadson在下面的评论中所做的非常漂亮的侦探工作。

尝试:

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

您可能需要在编译器选项中激活RTTI才能使其工作。 另外,这个的输出取决于编译器。 它可能是一个原始types的名称或名称改变符号或之间的任何东西。

不要忘记包含<typeinfo>

我相信你所指的是运行时types标识。 你可以通过这样做来达到上述目的。

 #include <iostream> #include <typeinfo> using namespace std; int main() { int i; cout << typeid(i).name(); return 0; } 

非常难看,但诀窍如果你只想编译时间信息(例如debugging):

 auto testVar = std::make_tuple(1, 1.0, "abc"); static_assert(decltype(testVar)::dummy_error, "DUMP MY TYPE" ); 

返回:

 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*>' 

请注意,由C ++的RTTI特性生成的名称是不可移植的。 例如,class级

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

将有以下名称:

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

所以你不能使用这些信息进行序列化。 但是,typeid(a).name()属性仍然可以用于日志/debugging目的

你可以使用模板。

 template <typename T> const char* typeof(T&) { return "unknown"; } // default template<> const char* typeof(int&) { return "int"; } template<> const char* typeof(float&) { return "float"; } 

在上面的例子中,当types不匹配时,它将打印“未知”。

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

}

你可以使用一个特质类。 就像是:

 #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定义的存在是为了让你的生活更轻松地为你期望需要的所有types声明这个traits类。

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

涉及RTTI(typeid)的其他答案可能是你想要的,只要:

  • 你可以承担内存开销(对于一些编译器来说这是相当可观的)
  • 你的编译器返回的类名是有用的

另一种方法(类似于Greg Hewgill的回答)是build立一个编译时间表。

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

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

要访问variablestypes的名称,只需要

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

在C ++ 11中,我们有decltype。 标准c ++没有办法显示使用decltype声明的variables的确切types。 我们可以使用boost typeindex即type_id_with_cvr (cvr代表const,volatile,reference)打印types如下。

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

没有函数重载比上一个更通用的解决scheme:

 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 

我喜欢Nick的方法,一个完整的表单可能是这样的(对于所有的基本数据types):

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

您也可以使用带有选项-t(type)的c ++ filt来取消types名称的拆分:

 #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上testing。

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

输出:

 Type of a: St16initializer_listIPKcE Real type of a: std::initializer_list<char const*> Type of s: PKc Real type of s: char const* one two three Type of i: i Real type of i: int 

在我面临挑战的时候,我决定testing一下可以走多远的平台独立(希望)模板欺骗。

名称在编译时完全组装。 (这意味着typeid(T).name()不能被使用,因此你必须明确地提供非复合types的名字,否则将会显示占位符。

用法示例:

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

码:

 #include <type_traits> #include <utility> static constexpr std::size_t max_str_lit_len = 256; template <std::size_t I, std::size_t N> constexpr char sl_at(const char (&str)[N]) { if constexpr(I < N) return str[I]; else return '\0'; } constexpr std::size_t sl_len(const char *str) { for (std::size_t i = 0; i < max_str_lit_len; i++) if (str[i] == '\0') return i; return 0; } template <char ...C> struct str_lit { static constexpr char value[] {C..., '\0'}; static constexpr int size = sl_len(value); template <typename F, typename ...P> struct concat_impl {using type = typename concat_impl<F>::type::template concat_impl<P...>::type;}; template <char ...CC> struct concat_impl<str_lit<CC...>> {using type = str_lit<C..., CC...>;}; template <typename ...P> using concat = typename concat_impl<P...>::type; }; template <typename, const char *> struct trim_str_lit_impl; template <std::size_t ...I, const char *S> struct trim_str_lit_impl<std::index_sequence<I...>, S> { using type = str_lit<S[I]...>; }; template <std::size_t N, const char *S> using trim_str_lit = typename trim_str_lit_impl<std::make_index_sequence<N>, S>::type; #define STR_LIT(str) ::trim_str_lit<::sl_len(str), ::str_lit<STR_TO_VA(str)>::value> #define STR_TO_VA(str) STR_TO_VA_16(str,0),STR_TO_VA_16(str,16),STR_TO_VA_16(str,32),STR_TO_VA_16(str,48) #define STR_TO_VA_16(str,off) STR_TO_VA_4(str,0+off),STR_TO_VA_4(str,4+off),STR_TO_VA_4(str,8+off),STR_TO_VA_4(str,12+off) #define STR_TO_VA_4(str,off) ::sl_at<off+0>(str),::sl_at<off+1>(str),::sl_at<off+2>(str),::sl_at<off+3>(str) template <char ...C> constexpr str_lit<C...> make_str_lit(str_lit<C...>) {return {};} template <std::size_t N> constexpr auto make_str_lit(const char (&str)[N]) { return trim_str_lit<sl_len((const char (&)[N])str), str>{}; } template <std::size_t A, std::size_t B> struct cexpr_pow {static constexpr std::size_t value = A * cexpr_pow<A,B-1>::value;}; template <std::size_t A> struct cexpr_pow<A,0> {static constexpr std::size_t value = 1;}; template <std::size_t N, std::size_t X, typename = std::make_index_sequence<X>> struct num_to_str_lit_impl; template <std::size_t N, std::size_t X, std::size_t ...Seq> struct num_to_str_lit_impl<N, X, std::index_sequence<Seq...>> { static constexpr auto func() { if constexpr (N >= cexpr_pow<10,X>::value) return num_to_str_lit_impl<N, X+1>::func(); else return str_lit<(N / cexpr_pow<10,X-1-Seq>::value % 10 + '0')...>{}; } }; template <std::size_t N> using num_to_str_lit = decltype(num_to_str_lit_impl<N,1>::func()); using spa = str_lit<' '>; using lpa = str_lit<'('>; using rpa = str_lit<')'>; using lbr = str_lit<'['>; using rbr = str_lit<']'>; using ast = str_lit<'*'>; using amp = str_lit<'&'>; using con = str_lit<'c','o','n','s','t'>; using vol = str_lit<'v','o','l','a','t','i','l','e'>; using con_vol = con::concat<spa, vol>; using nsp = str_lit<':',':'>; using com = str_lit<','>; using unk = str_lit<'?','?'>; using c_cla = str_lit<'c','l','a','s','s','?'>; using c_uni = str_lit<'u','n','i','o','n','?'>; using c_enu = str_lit<'e','n','u','m','?'>; template <typename T> inline constexpr bool ptr_or_ref = std::is_pointer_v<T> || std::is_reference_v<T> || std::is_member_pointer_v<T>; template <typename T> inline constexpr bool func_or_arr = std::is_function_v<T> || std::is_array_v<T>; template <typename T> struct primitive_type_name {using value = unk;}; template <typename T, typename = std::enable_if_t<std::is_class_v<T>>> using enable_if_class = T; template <typename T, typename = std::enable_if_t<std::is_union_v<T>>> using enable_if_union = T; template <typename T, typename = std::enable_if_t<std::is_enum_v <T>>> using enable_if_enum = T; template <typename T> struct primitive_type_name<enable_if_class<T>> {using value = c_cla;}; template <typename T> struct primitive_type_name<enable_if_union<T>> {using value = c_uni;}; template <typename T> struct primitive_type_name<enable_if_enum <T>> {using value = c_enu;}; template <typename T> struct type_name_impl; template <typename T> using type_name_lit = std::conditional_t<std::is_same_v<typename primitive_type_name<T>::value::template concat<spa>, typename type_name_impl<T>::l::template concat<typename type_name_impl<T>::r>>, typename primitive_type_name<T>::value, typename type_name_impl<T>::l::template concat<typename type_name_impl<T>::r>>; template <typename T> inline constexpr const char *type_name = type_name_lit<T>::value; template <typename T, typename = std::enable_if_t<!std::is_const_v<T> && !std::is_volatile_v<T>>> using enable_if_no_cv = T; template <typename T> struct type_name_impl { using l = typename primitive_type_name<T>::value::template concat<spa>; using r = str_lit<>; }; template <typename T> struct type_name_impl<const T> { using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>, spa::concat<typename type_name_impl<T>::l>, typename type_name_impl<T>::l>; using l = std::conditional_t<ptr_or_ref<T>, typename new_T_l::template concat<con>, con::concat<new_T_l>>; using r = typename type_name_impl<T>::r; }; template <typename T> struct type_name_impl<volatile T> { using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>, spa::concat<typename type_name_impl<T>::l>, typename type_name_impl<T>::l>; using l = std::conditional_t<ptr_or_ref<T>, typename new_T_l::template concat<vol>, vol::concat<new_T_l>>; using r = typename type_name_impl<T>::r; }; template <typename T> struct type_name_impl<const volatile T> { using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>, spa::concat<typename type_name_impl<T>::l>, typename type_name_impl<T>::l>; using l = std::conditional_t<ptr_or_ref<T>, typename new_T_l::template concat<con_vol>, con_vol::concat<new_T_l>>; using r = typename type_name_impl<T>::r; }; template <typename T> struct type_name_impl<T *> { using l = std::conditional_t<func_or_arr<T>, typename type_name_impl<T>::l::template concat<lpa, ast>, typename type_name_impl<T>::l::template concat< ast>>; using r = std::conditional_t<func_or_arr<T>, rpa::concat<typename type_name_impl<T>::r>, typename type_name_impl<T>::r>; }; template <typename T> struct type_name_impl<T &> { using l = std::conditional_t<func_or_arr<T>, typename type_name_impl<T>::l::template concat<lpa, amp>, typename type_name_impl<T>::l::template concat< amp>>; using r = std::conditional_t<func_or_arr<T>, rpa::concat<typename type_name_impl<T>::r>, typename type_name_impl<T>::r>; }; template <typename T> struct type_name_impl<T &&> { using l = std::conditional_t<func_or_arr<T>, typename type_name_impl<T>::l::template concat<lpa, amp, amp>, typename type_name_impl<T>::l::template concat< amp, amp>>; using r = std::conditional_t<func_or_arr<T>, rpa::concat<typename type_name_impl<T>::r>, typename type_name_impl<T>::r>; }; template <typename T, typename C> struct type_name_impl<TC::*> { using l = std::conditional_t<func_or_arr<T>, typename type_name_impl<T>::l::template concat<lpa, type_name_lit<C>, nsp, ast>, typename type_name_impl<T>::l::template concat< type_name_lit<C>, nsp, ast>>; using r = std::conditional_t<func_or_arr<T>, rpa::concat<typename type_name_impl<T>::r>, typename type_name_impl<T>::r>; }; template <typename T> struct type_name_impl<enable_if_no_cv<T[]>> { using l = typename type_name_impl<T>::l; using r = lbr::concat<rbr, typename type_name_impl<T>::r>; }; template <typename T, std::size_t N> struct type_name_impl<enable_if_no_cv<T[N]>> { using l = typename type_name_impl<T>::l; using r = lbr::concat<num_to_str_lit<N>, rbr, typename type_name_impl<T>::r>; }; template <typename T> struct type_name_impl<T()> { using l = typename type_name_impl<T>::l; using r = lpa::concat<rpa, typename type_name_impl<T>::r>; }; template <typename T, typename P1, typename ...P> struct type_name_impl<T(P1, P...)> { using l = typename type_name_impl<T>::l; using r = lpa::concat<type_name_lit<P1>, com::concat<type_name_lit<P>>..., rpa, typename type_name_impl<T>::r>; }; #define TYPE_NAME(t) template <> struct primitive_type_name<t> {using value = STR_LIT(#t);};