代码之家  ›  专栏  ›  技术社区  ›  Jorge Ferreira

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

  •  296
  • Jorge Ferreira  · 技术社区  · 16 年前

    例如:

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

    预期输出:

    int
    
    15 回复  |  直到 16 年前
        1
  •  375
  •   Howard Hinnant    7 年前

    typeid(a).name() a

    decltype(x) decltype() decltype(a) decltype((a))

    typeid(a).name()
    

    const int ci = 0;
    std::cout << typeid(ci).name() << '\n';
    

    i
    

    int
    

    const

    template <typename T> std::string type_name();
    

    const int ci = 0;
    std::cout << type_name<decltype(ci)>() << '\n';
    

    int const
    

    <disclaimer> </disclaimer>

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

    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 decltype

    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
    

    This answer Jamboree

    Jamboree's answer

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

    constexpr noexcept

    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
    }
    

        2
  •  217
  •   Konrad Rudolph    11 年前

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

        3
  •  60
  •   jcsahnwaldt Reinstate Monica    6 年前

    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*>'
    
        4
  •  51
  •   BartoszKP    8 年前

    <typeinfo>

    #include <iostream>
    #include <typeinfo>
    
    using namespace std;
    
    int main() {
      int i;
      cout << typeid(i).name();
      return 0;
    }
    
        5
  •  22
  •   paercebal    16 年前

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

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

        6
  •  18
  •   Nick    16 年前

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

        7
  •  14
  •   ipapadop    12 年前

    typeid().name()

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

        8
  •  10
  •   Greg Hewgill    16 年前

    #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 long long

        9
  •  6
  •   Jonas    7 年前

    type_id_with_cvr

    #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;
    }
    
        10
  •  5
  •   James Hopkin    14 年前

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

    template <typename T>
    const char* get_type_as_string(const T&)
    {
        return type_as_string<T>::value;
    }
    
        11
  •  4
  •   Jahid Ramsy de Vos    10 年前

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

    #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
  •   Jahid Ramsy de Vos    10 年前

    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
  •   Alan    9 年前

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

        14
  •  1
  •   Graywolf    7 年前
    #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
    
        15
  •  1
  •   HolyBlackCat    7 年前

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

    #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<T C::*>
    {
        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);};