C++ | 変数やパラメータの型を出力する【gcc, clang, Visual Studio】

C++ を実装していると、auto 変数や template パラメータなどに対してコンパイル時の型が知りたくなることがあります。
例えば、 Effective Modern C++ の Item1 としてテンプレートの型推論の解説があり、次のような雰囲気のコードがあります。

template <typename T>
void f(T &&param) {}

int main() {
    int x = 27;
    const int cx = x;
    const int &rx = x;
    
    f(x);
    f(cx);
    f(rx);
    f(27);
    
    return 0;
}

これは、各関数呼び出しにて、T と param の型がどのように推論されるかを例示するためのコードです。
しかし、型名の出力方法には言及されておらず、自分で動作させて確認することができません。

本記事では、コンパイル時に決定される型情報を標準出力する方法を紹介します。

目次

typeid 演算子(機能性は不十分)

最も簡易的な方法として、typeid 演算子を用いるものがあります。
しかし機能性が不十分です。

以下は const int & の変数 x に対して型を出力する例となりますが、const や & の修飾情報が欠落しています。
int そのものもマングルされてしまい、i という出力になっています。

#include <iostream>

int main()
{
    const int &x = 27;
    std::cout << typeid(x).name() << std::endl;
    return 0;
}
$ g++ main.cc && ./a.out
i

以下は Visual Studio での実行例です。
int はそのまま出力されるものの、修飾詞は省略されてしまっています。

boost の type_name

Boost が利用できれば、利便性高く型情報を出力可能です。

#include <iostream>
#include <boost/core/type_name.hpp>

int main()
{
    const int x = 27;
    const int &rx = x;
    const int *px1 = &x;
    const int *const px2 = &x;
    std::cout << boost::core::type_name<decltype(x)>() << std::endl;
    std::cout << boost::core::type_name<decltype(rx)>() << std::endl;
    std::cout << boost::core::type_name<decltype(px1)>() << std::endl;
    std::cout << boost::core::type_name<decltype(px2)>() << std::endl;
    return 0;
}
$ g++ main.cc && ./a.out
int const
int const&
int const*
int const* const

const の位置が見慣れない場所にあるのが、やや難点です。
なお、C++ の仕様上const int &int const & は同じ意味です。

記事冒頭の例に適用すると、tempalte の型推論結果を視認することができます。

#include <iostream>
#include <boost/core/type_name.hpp>

template <typename T>
void f(T &&param) 
{
    std::cout << "T    : " << boost::core::type_name<T>() << std::endl;
    std::cout << "param: " << boost::core::type_name<decltype(param)>() << std::endl;
    std::cout << std::endl;
}

int main()
{
    int x = 27;
    const int cx = x;
    const int &rx = x;
    
    f(x);   // lvalue
    f(cx);  // lvalue
    f(rx);  // lvalue
    f(27);  // rvalue
    
    return 0;
}
$ g++ main.cc && ./a.out
T    : int&
param: int&

T    : int const&
param: int const&

T    : int const&
param: int const&

T    : int
param: int&&

ファンクションの実行情報を利用(Boost がない場合)

Boost がない場合は、同様のメソッドを自前で用意する必要があります。
以下、C++ バージョンごとに gcc, clang, Visual Studio の実装例を示します。

コンパイラの C++ バージョンを確認したい場合は、次のリンク記事参照です。

C++20

gcc, clang

source_location の function_name を利用します。

#include <iostream>
#include <source_location>

template <typename T>
consteval auto type_name()
{
    std::string_view s = std::source_location::current().function_name();
    const auto start = s.find("T = ") + 4;
    const auto end = s.find(']');
    return s.substr(start, end - start);
}

int main()
{
    const int &x = 27;
    std::cout << type_name<decltype(x)>() << std::endl;
}
$ g++ -std=c++20 main.cc && ./a.out
const int&

function_name からは次のような文字が得られますので、これを解析しています。

# gcc
consteval auto type_name() [with T = const int&]

# clang
auto type_name() [T = const int &]

Visual Studio C++

function_name の解析方法が異なります。

#include <iostream>
#include <source_location>

template <typename T>
consteval auto type_name()
{
    std::string_view s = std::source_location::current().function_name();
    const auto start = s.find("type_name<") + 10;
    const auto end = s.find(">(void)");
    return s.substr(start, end - start);
}

int main()
{
    const int& x = 27;
    std::cout << type_name<decltype(x)>() << std::endl;
}

function_name から得られる文字列は次のようになっています。

class std::basic_string_view<char,struct std::char_traits<char> > __cdecl type_name<const int&>(void)

C++17

gcc

gcc の場合は、__PRETTY_FUNCTION__ マクロを解析して出力します。
ここにはメソッドの情報が引数などを含め表現されています。

C++
#include <iostream>

template <typename T>
constexpr std::string_view type_name()
{
    std::string_view s(__PRETTY_FUNCTION__);
    const auto start = s.find("T = ") + 4;
    const auto end = s.find(';');
    return s.substr(start, end - start);
}

int main()
{
    const int &x = 27;
    std::cout << type_name<decltype(x)>() << std::endl;;
}
$ g++ -std=c++17 main.cc && ./a.out
const int&

上記の例では、次のような文字列がマクロに格納されています。

constexpr std::string_view type_name() [with T = const int&; std::string_view = std::basic_string_view<char>]

なお、string_view の利用は C++17 以降になりますので、C++14 においては string に変更するなどの対処が必要です。

clang

gcc と同様のマクロを利用しますが、設定値が異なります。
function_name と同様の形式の設定値を用いることになります。

C++
#include <iostream>

template <typename T>
constexpr std::string_view type_name()
{
    std::string_view s(__PRETTY_FUNCTION__);
    const auto start = s.find("T = ") + 4;
    const auto end = s.find(']');
    return s.substr(start, end - start);
}

int main()
{
    const int &x = 27;
    std::cout << type_name<decltype(x)>() << std::endl;
}
$ g++ -std=c++17 main.cc && ./a.out
const int&

以下はマクロの文字列例です。

std::string_view type_name() [T = const int &]

C++14 においては、string_view を string に変更する対処が必要です。
加えて、clang ではこれでもエラーが発生してしまい、constexpr の除去も必要でした。

Visual Studio C++

Visual Studio においては、 __FUNCSIG__ マクロを参照します。
function_name と同様の形式の文字列を取得することができます。

#include <iostream>

template <typename T>
std::string_view type_name()
{
    std::string_view s(__FUNCSIG__);
    std::size_t start = s.find("type_name<") + 10;
    std::size_t end = s.find(">(void)");
    return s.substr(start, end - start);
}

int main()
{
    const int &x = 27;
    std::cout << type_name<decltype(x)>() << std::endl;
}

以下は、マクロの文字列例です。

class std::basic_string_view<char,struct std::char_traits<char> > __cdecl type_name<const int&>(void)

C++14 の場合は、string_view を string に変更するよう対処します。

  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次