ことの発端
typeid().name()
で取得できるクラス名ってマングリング化されていて読みにくいことは知っていた。
で、ちとデバッグ時にクラス名をログに記録したいということがあり、デマングリングを自作しようと調べていたところ、デマングリングしてくれる関数があることを知りました。
https://gintenlabo.hatenablog.com/entry/20100116/1263681145
その後、api仕様も確認したうえで。
https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html
こんな感じで書いてみて。。。
# include <iostream>
# include <cxxabi.h> /** abi::__cxa_demangle */
template <typename T> std::string className(T* cls) {
int status;
char* cname = abi::__cxa_demangle(typeid(*cls).name(), nullptr, nullptr, &status);
const std::string s = (cname && (status == 0)) ? cname : "";
::free(cname);
return s;
}
int main(void){
struct A { virtual ~A() = default; };
struct B : public A {};
B b;
A* a = &b;
std::cout << className(a);
}
main::B
あ〜、free() とか出現してるな〜
C++使っているなら、常に例外を意識しなきゃならんよね。
んで、返却値の s を生成する際に例外が発生したらメモリリークするから、きっとツッコミが入るに違いない。
ということで、
# include <iostream>
# include <memory>
# include <cxxabi.h> /** abi::__cxa_demangle */
template <typename T> std::string className(T* cls) {
int status;
struct deleter_t {
void operator() (char* p) const { ::free(p); }
};
std::unique_ptr<char, deleter_t> cname(abi::__cxa_demangle(typeid(*cls).name(), nullptr, nullptr, &status));
return (cname && (status == 0)) ? cname.get() : "";
}
int main(void){
struct A { virtual ~A() = default; };
struct B : public A {};
B b;
A* a = &b;
std::cout << className(a);
}
こんなコードにしてみて、終了と思ったんですが。。。
そういえば、std::auto_ptr
がご健在だった頃、外部DLLでランタイムが異なるアロケータで生成されたポインタの解放をするため、deleter書いたことあったなぁ〜
template の値仮引数って関数ポインタ使えるんだから、きっと、うまくやれば < >
内に解放関数書けるかも!
早速書いてみた
# include <iostream>
# include <memory>
# include <cxxabi.h> /** abi::__cxa_demangle */
template <void(*DEL)(char*)> struct deleter_t {
void operator () (char* p) const { DEL(p); }
};
template <typename T> std::string className(T* cls) {
int status;
std::unique_ptr<char, deleter_t<[](char* p){ ::free(p); }>> cname(abi::__cxa_demangle(typeid(*cls).name(), nullptr, nullptr, &status));
return (cname && (status == 0)) ? cname.get() : "";
}
int main(void){
struct A { virtual ~A() = default; };
struct B : public A {};
B b;
A* a = &b;
std::cout << className(a);
}
std::unique_ptr
テンプレート仮引数に関数ポインタを渡せればよいのですが、そんな事できないので、結果的に delete_t を定義しなきゃならないし、この時点で可読性悪いし、もうやめようかと。。。
で、結果はエラーです。
(ちなみにstruct deleter_t
なんて定義しているのがアホだと後で知ります)
Main.cpp:11:35: error: a lambda expression cannot appear in this context
std::unique_ptr<char, deleter_t<[](char* p){ ::free(p); }>> cname(abi::__cxa_demangle(typeid(*cls).name(), nullptr, nullptr, &status));
^
エラーを見ると、< >
内にラムダ式なんか書くなよって事みたいなので、もしかすると( )
を仲介すればワンチャンあるかも!
# include <iostream>
# include <memory>
# include <cxxabi.h> /** abi::__cxa_demangle */
template <void(*DEL)(char*)> struct deleter_t {
void operator () (char* p) const { DEL(p); }
};
template <typename T> constexpr T PASSTHRU(T&& t) { return t; }
template <typename T> std::string className(T* cls) {
int status;
std::unique_ptr<char, deleter_t<PASSTHRU([](char* p){ ::free(p); })>> cname(abi::__cxa_demangle(typeid(*cls).name(), nullptr, nullptr, &status));
return (cname && (status == 0)) ? cname.get() : "";
}
int main(void){
struct A { virtual ~A() = default; };
struct B : public A {};
B b;
A* a = &b;
std::cout << className(a);
}
だめだ。。。
これでも誤魔化せない。。。
ちなみに、これならコンパイルが通るので、< >
内でラムダ式は書いちゃダメってことですね。
# include <iostream>
# include <memory>
# include <cxxabi.h> /** abi::__cxa_demangle */
template <void(*DEL)(char*)> struct deleter_t {
void operator () (char* p) const { DEL(p); }
};
template <typename T> constexpr T PASSTHRU(T&& t) { return t; }
template <typename T> std::string className(T* cls) {
int status;
const auto d = PASSTHRU([](char* p){ ::free(p); });
std::unique_ptr<char, deleter_t<d>> cname(abi::__cxa_demangle(typeid(*cls).name(), nullptr, nullptr, &status));
return (cname && (status == 0)) ? cname.get() : "";
}
int main(void){
struct A { virtual ~A() = default; };
struct B : public A {};
B b;
A* a = &b;
std::cout << className(a);
}
答え合わせ
ここで、さんざん楽しんだ挙げ句、いつものストラウストラップ氏著の「プログラミング言語C++」を見てみる。
まず、ラムダ式が糖衣構文であることは知っていたが std::function
を作ると思っていたが誤りだった。。。
「§11.4 ラムダ式」の冒頭には
ラムダ式は名前付きクラスにoperator()を定義して、そのクラスオブジェクトを作成
つまり、struct deleter_t
なんて不要だということですね。
std::unique_ptr
の型定義を追っかけていて、struct に operator() なんて回りくどい定義してるんだろうと思ったけど、こういうことだたんか。。。 と、今更知る。
正直、この記事書いていて恥ずかしい。。。
お次は、template の< >
文脈でのラムダ式については、「§25.2.3 引数としての処理」にありました。
ラムダ式から関数オブジェクト型への変換が存在しない
ラムダ式って、クラスオブジェクトを作成するから、ここで関数オブジェクトに変換した日にゃ〜、本来 operator() を抱えている this はどこへやらですね。なるほど。。。 1 2
う〜ん、となるとこんな感じになるのかな? 3
# include <iostream>
# include <memory>
# include <cxxabi.h> /** abi::__cxa_demangle */
template <typename T> std::string className(T* cls) {
int status;
const auto deleter = [](char* p){ ::free(p); };
std::unique_ptr<char, decltype(deleter)> cname(abi::__cxa_demangle(typeid(*cls).name(), nullptr, nullptr, &status));
return (cname && (status == 0)) ? cname.get() : "";
}
int main(void){
struct A { virtual ~A() = default; };
struct B : public A {};
B b;
A* a = &b;
std::cout << className(a);
}
template の< >
の文脈でラムダ式という当初の目的は逸脱しているけど、スッキリしていなくはないか。
戒め
まだまだいい加減な理解や知識が多い。
。。。だけど、a lambda expression cannot appear in this context
ってエラーメッセージは、ちょっとミスリードじゃない? とか言ってみる。
-
そもそも「関数オブジェクト」の言葉の定義が曖昧だったことに気付き、定義を調べたら、
operator()
が定義されているオブジェクトということなので、ちょっと理解が足りていないコメントでした。そうなると、一時オブジェクトの型変換だから出来ないって事なのか悩み中。(2021/02/25追記・本文削除) ↩ -
一晩おいて考えると、「ラムダ式は【クラスオブジェクト】を作成する」んだから、「関数オブジェクト【型】」に変換できるわけないなと。(2021/02/26追記) ↩
-
template パラメータ内の
decltype
はやってみたら動いたってレベルなので、何でこれで動くのか残念ながらまだ理解できていないので要調査。(2021/02/25追記) ↩