Is it possible to print a variable's type in standard C++?

前端 未结 21 1683
南笙
南笙 2020-11-22 01:41

For example:

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

Expected output:

int
相关标签:
21条回答
  • 2020-11-22 02:05

    As I challenge I decided to test how far can one go with platform-independent (hopefully) template trickery.

    The names are assembled completely at compilation time. (Which means typeid(T).name() couldn't be used, thus you have to explicitly provide names for non-compound types. Otherwise placeholders will be displayed instead.)

    Example usage:

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

    Code:

    #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);};
    
    0 讨论(0)
  • 2020-11-22 02:06

    Copying from this answer: https://stackoverflow.com/a/56766138/11502722

    I was able to get this somewhat working for C++ static_assert(). The wrinkle here is that static_assert() only accepts string literals; constexpr string_view will not work. You will need to accept extra text around the typename, but it works:

    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 Output:

    error C2338: Test failed on this used type: void __cdecl assertIfTestFailed<class BadType>(void)
    ... continued trace of where the erroring code came from ...
    
    0 讨论(0)
  • 2020-11-22 02:09

    Don't forget to include <typeinfo>

    I believe what you are referring to is runtime type identification. You can achieve the above by doing .

    #include <iostream>
    #include <typeinfo>
    
    using namespace std;
    
    int main() {
      int i;
      cout << typeid(i).name();
      return 0;
    }
    
    0 讨论(0)
  • 2020-11-22 02:09

    In C++11, we have decltype. There is no way in standard c++ to display exact type of variable declared using decltype. We can use boost typeindex i.e type_id_with_cvr (cvr stands for const, volatile, reference) to print type like below.

    #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;
    }
    
    0 讨论(0)
  • 2020-11-22 02:10

    You can use templates.

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

    In the example above, when the type is not matched it will print "unknown".

    0 讨论(0)
  • 2020-11-22 02:11

    As mentioned, typeid().name() may return a mangled name. In GCC (and some other compilers) you can work around it with the following code:

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

    }

    0 讨论(0)
提交回复
热议问题