LoginSignup
5
6

More than 1 year has passed since last update.

C++リフレクション~クラス名を文字列として取得する~

Last updated at Posted at 2022-12-24

方針

元のコードに対して何か加えるようなことはしない

外部ライブラリを使っている場合、元のクラス定義に手動でクラス名を文字列として定義するようなことはできない。

struct SomeStruct{
    /*なんらかの実装*/
    static constexpr std::string_view name = "SomeStruct";
};

typoで構造体名と文字列が違うというミスを犯す可能性があるので却下。

テンプレート引数で渡された型に対しても使える

以下のようなマクロを定義すれば一応できているようには見える。

#define ToString(X) #X

struct SomeStruct{};

int main(){
    std::cout << ToString(SomeStruct) << std::endl;  // "SomeStruct"と表示される
}

しかし、テンプレート引数に対しては無力である。

#define ToString(X) #X

struct SomeStruct{};

template<typename T>
void func(){
    std::cout << ToString(T) << std::endl; // "T"と表示される
}
int main(){
    func<SomeStruct>();
}

これでは、意味がないのでテンプレート引数に対しても正しく表示できるような実装を目指す。

実装方法

C++には__func__という事前定義識別子がある。これは関数名を文字列として取得できる。

struct DDD{};

template<typename T>
void IwantFuncName(){
    std::cout << __func__ << std::endl; // "IwantFuncName"と表示される
}

int main(){
    IwantFuncName<DDD>();
}

しかし、(実装定義ではあるが)関数名だけであるので戻り値型や引数、テンプレート引数などは表示されない。

gccとclangには似たような識別子として__PRETTY_FUNCTION__というものがあり、これは__func__では表示されなかったテンプレート引数などを事細かに文字列として取得できる。

struct DDD{};

template<typename T>
void IwantFuncName(){
    std::cout << __PRETTY_FUNCTION__ << std::endl;
    // gccだと"void IwantFuncName() [with T = DDD]"
    // clangだと"void IwantFuncName() [T = DDD]"
}

int main(){
    IwantFuncName<DDD>();
}

gccとclangでは上記の方法で大丈夫だが、MSVCは__FUNCDNAME__はマングリングされた名前となり、__FUNCTION__では非メンバテンプレート関数の場合、関数名しか取得できない。

struct DDD {};

template<typename T>
void IwantFuncName() {
    std::cout << __FUNCTION__ << std::endl; // "IwantFuncName"と表示される
}

int main() {
    IwantFuncName<DDD>();
}

しかし、__FUNCTION__で取得する関数がクラステンプレートの(静的)メンバ関数の場合、クラステンプレートのテンプレート引数に渡されたクラス名が取得できるようになる。

struct DDD {};

template<typename T>
struct class_name{
    static void IwantFuncName() {
        std::cout << __FUNCTION__ << std::endl;
        // "class_name<struct DDD>::IwantFuncName"と表示される
    }
};


int main() {
    class_name<DDD>{}.IwantFuncName();
}

それぞれ取得できる文字列はコンパイラ次第なので、これを処理してやることでテンプレート引数に渡した型名が取得できる。
ゴリゴリに実装依存では?

実装

template<typename D>
class class_name {
private:
    static constexpr auto impl(){
        #ifdef _MSC_VER
        constexpr auto str_ptr = __FUNCTION__;
        constexpr auto base_class_len = std::char_traits<char>::length("class_name<");
        constexpr auto head_len = base_class_len + std::char_traits<char>::length(str_ptr[base_class_len] == 's' ? "struct " : "class ");
        constexpr auto tail_len = std::char_traits<char>::length(">::impl");
        #elif defined(__clang__)
        constexpr auto str_ptr = __PRETTY_FUNCTION__;
        constexpr auto head_len = std::char_traits<char>::find(str_ptr, std::char_traits<char>::length(str_ptr), '[') - str_ptr + std::char_traits<char>::length("[D = ");
        constexpr auto tail_len = std::char_traits<char>::length("]");
        #elif defined(__GNUC__)
        constexpr auto str_ptr = __PRETTY_FUNCTION__;
        constexpr auto head_len = std::char_traits<char>::find(str_ptr, std::char_traits<char>::length(str_ptr), '[') - str_ptr + std::char_traits<char>::length("[with D = ");
        constexpr auto tail_len = std::char_traits<char>::length("]");
        #endif
        constexpr auto len = std::char_traits<char>::length(str_ptr);
        return std::string_view(&str_ptr[head_len], len - head_len - tail_len);
    }
public:
    static constexpr auto value = impl();
};

コード全体

用例

struct SomeStruct{};

int main(){
    std::cout << class_name<SomeStruct>::value << std::endl; // "SomeStruct"と表示される
}

もちろんテンプレート引数に対しても正確に取得できる。

struct SomeStruct{};

template<typename T>
void func(){
    std::cout << class_name<T>::value << std::endl; // "SomeStruct"と表示される
}

int main(){
    func<SomeStruct>();
}
5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6