1. ホーム
  2. c++

[解決済み】標準C++で変数の型を表示することは可能ですか?

2022-03-23 04:44:40

質問

例えば

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

期待される出力

int

解決方法は?

非常に古い質問に対する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(Quality Of Implementation)の問題ではありません。 規格がこの動作を義務付けているのです。

以下、私が推奨しているのは

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の解決策

を使っています。 __cxa_demangle が推奨する非MSVCプラットフォーム用の イパパドップ の回答で、demangleの種類を指定しています。 しかし、MSVCでは、私は typeid を使用して名前をデマングルします(未検証)。 そして、このコアは、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;
}

結果

このソリューションで、私はこんなことができるようになりました。

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 . 後者は、quot;type" である。 表現 i . (式が参照型を持つことはありませんが、慣例として decltype はlvalueの式をlvalueの参照で表現しています)。

このように、このツールは単に decltype また、自分自身のコードの調査やデバッグを行うこともできます。

これに対して、もし私がこれを typeid(a).name() 失われた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アップデート

ある問題を解決できたと思ったとき、いつも誰かがどこからともなくやってきて、もっといい方法を教えてくれる :-)

この答え から ジャンボリー は、C++14でコンパイル時に型名を取得する方法を示しています。 いくつかの理由から、これは見事な解決策である。

  1. コンパイル時です!
  2. ライブラリ(std::libでも)ではなく、コンパイラ自身が仕事をするようになるのです。 つまり、最新の言語機能(ラムダなど)に対して、より正確な結果が得られるということです。

ジャンボリーの 回答 は、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
}

このコードでは、自動バックオフを constexpr もし、あなたがまだ古代のC++11から抜け出せずにいるならば。 また、C++98/03で洞窟の壁に絵を描いているのならば noexcept も犠牲になる。

C++17のアップデート

以下のコメントにて リベルタ が新しくなったことを指摘しています。 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用の定数を更新しました。

更新しました。

を必ずご確認ください。 このリライトは以下の通りです。 で、私の最新の処方では、読めないマジックナンバーを排除しています。