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

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

For example:

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

Expected output:

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

    You may also use c++filt with option -t (type) to demangle the type name:

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

    Tested on linux only.

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

    C++11 update to a very old question: Print variable type in C++.

    The accepted (and good) answer is to use typeid(a).name(), where a is a variable name.

    Now in C++11 we have decltype(x), which can turn an expression into a type. And decltype() comes with its own set of very interesting rules. For example decltype(a) and decltype((a)) will generally be different types (and for good and understandable reasons once those reasons are exposed).

    Will our trusty typeid(a).name() help us explore this brave new world?

    No.

    But the tool that will is not that complicated. And it is that tool which I am using as an answer to this question. I will compare and contrast this new tool to typeid(a).name(). And this new tool is actually built on top of typeid(a).name().

    The fundamental issue:

    typeid(a).name()
    

    throws away cv-qualifiers, references, and lvalue/rvalue-ness. For example:

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

    For me outputs:

    i
    

    and I'm guessing on MSVC outputs:

    int
    

    I.e. the const is gone. This is not a QOI (Quality Of Implementation) issue. The standard mandates this behavior.

    What I'm recommending below is:

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

    which would be used like this:

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

    and for me outputs:

    int const
    

    <disclaimer> I have not tested this on MSVC. </disclaimer> But I welcome feedback from those who do.

    The C++11 Solution

    I am using __cxa_demangle for non-MSVC platforms as recommend by ipapadop in his answer to demangle types. But on MSVC I'm trusting typeid to demangle names (untested). And this core is wrapped around some simple testing that detects, restores and reports cv-qualifiers and references to the input type.

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

    The Results

    With this solution I can do this:

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

    and the output is:

    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
    

    Note (for example) the difference between decltype(i) and decltype((i)). The former is the type of the declaration of i. The latter is the "type" of the expression i. (expressions never have reference type, but as a convention decltype represents lvalue expressions with lvalue references).

    Thus this tool is an excellent vehicle just to learn about decltype, in addition to exploring and debugging your own code.

    In contrast, if I were to build this just on typeid(a).name(), without adding back lost cv-qualifiers or references, the output would be:

    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
    

    I.e. Every reference and cv-qualifier is stripped off.

    C++14 Update

    Just when you think you've got a solution to a problem nailed, someone always comes out of nowhere and shows you a much better way. :-)

    This answer from Jamboree shows how to get the type name in C++14 at compile time. It is a brilliant solution for a couple reasons:

    1. It's at compile time!
    2. You get the compiler itself to do the job instead of a library (even a std::lib). This means more accurate results for the latest language features (like lambdas).

    Jamboree's answer doesn't quite lay everything out for VS, and I'm tweaking his code a little bit. But since this answer gets a lot of views, take some time to go over there and upvote his answer, without which, this update would never have happened.

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

    This code will auto-backoff on the constexpr if you're still stuck in ancient C++11. And if you're painting on the cave wall with C++98/03, the noexcept is sacrificed as well.

    C++17 Update

    In the comments below Lyberta points out that the new std::string_view can replace 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
    }
    

    I've updated the constants for VS thanks to the very nice detective work by Jive Dadson in the comments below.

    Update:

    Be sure to check out this rewrite below which eliminates the unreadable magic numbers in my latest formulation.

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

    A more generic solution without function overloading than my previous one:

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

    Here MyClass is user defined class. More conditions can be added here as well.

    Example:

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

    Output:

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

    Output:

    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
    
    0 讨论(0)
  • 2020-11-22 02:26

    Note that the names generated by the RTTI feature of C++ is not portable. For example, the class

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

    will have the following names:

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

    So you can't use this information for serialization. But still, the typeid(a).name() property can still be used for log/debug purposes

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

    As explained by Scott Meyers in Effective Modern C++,

    Calls to std::type_info::name are not guaranteed to return anythong sensible.

    The best solution is to let the compiler generate an error message during the type deduction, for example,

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

    The result will be something like this, depending on the compilers,

    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;
    

    Hence, we get to know that x's type is int, y's type is const int*

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