2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

template パラメータ内でラムダ式

Last updated at Posted at 2021-02-25

ことの発端

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 ってエラーメッセージは、ちょっとミスリードじゃない? とか言ってみる。

  1. そもそも「関数オブジェクト」の言葉の定義が曖昧だったことに気付き、定義を調べたら、operator() が定義されているオブジェクトということなので、ちょっと理解が足りていないコメントでした。そうなると、一時オブジェクトの型変換だから出来ないって事なのか悩み中。(2021/02/25追記・本文削除)

  2. 一晩おいて考えると、「ラムダ式は【クラスオブジェクト】を作成する」んだから、「関数オブジェクト【型】」に変換できるわけないなと。(2021/02/26追記)

  3. template パラメータ内の decltype はやってみたら動いたってレベルなので、何でこれで動くのか残念ながらまだ理解できていないので要調査。(2021/02/25追記)

2
2
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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?