C ++:C ++機能ではなくコンパイラAPIを使用したメタプログラミング


10

これはSOの質問として始まりましたが、それはかなり型破りであり、Webサイト上の実際の説明に基づくと、質問には多くの概念的な重みがあるため、programmers.seに適している可能性があることに気付きました。

私はclang LibToolingを学習してきましたが、これは非常に強力なツールであり、コードの「ぎこちない」全体をフレンドリーな方法で、つまりセマンティックな方法で、推測することなく公開できます。clangがコードをコンパイルできる場合、clangはそのコード内のすべての単一文字のセマンティクスについて確実です。

ここで、少し前に戻ります。

C ++テンプレートのメタプログラミングに従事する場合(特に、恐ろしいマクロではあるが、テンプレートを超えて賢い領域に冒険する場合)には、多くの実用的な問題が発生します。正直なところ、私も含めて多くのプログラマーにとって、テンプレートの通常の使用法の多くは、やや恐ろしいものです。

良い例は、コンパイル時の文字列だと思います。これは、1年以上前の質問ですが、現時点でのC ++では、これが単なる人間にとって容易ではないことは明らかです。これらのオプションを見るだけでは吐き気を誘発するのに十分ではありませんが、それでも、私が自分のソフトウェアで使用している空想のアプリケーションに適合する魔法の、最大限に効率的なマシンコードを生成できるかどうか自信がありません。

正直言って、皆さん、文字列はかなりシンプルで基本的なものです。私たちの中には、特定の文字列が単純な方法でコーディングする場合よりも大幅に「組み込まれた」マシンコードを生成する便利な方法を求めているだけです。私たちのC ++コードで。

clangとLibToolingを入力します。これにより、ソースコードの抽象構文ツリー(AST)が公開され、シンプルなカスタムC ++アプリケーションRewriterが、ASTのすべての豊富なセマンティックオブジェクト指向モデルと一緒に、生のソースコードを(確実に)正しく正確に操作できるようになります。それは多くのものを扱います。マクロ展開について知っているので、それらのチェーンをたどることができます。はい、私はソースからソースへのコード変換または変換について話しています。

ここでの私の基本的な論点は、clangにより、C ++ソフトウェアの理想的なカスタムプリプロセッサステージとして機能する実行可能ファイルを作成できるようになり、これらのメタプログラミングステージをC ++で実装できるということです。このステージは、有効なC ++コードである入力を受け取り、より有効なC ++コードを出力として生成する必要があるという事実に制約されます。さらに、ビルドシステムが適用するその他の制約。

結局のところ、clangはコンパイラのフロントエンドであり、APIを使って独創的であるため、入力は少なくとも有効なC ++コードに非常に近い必要があります。使用する新しい構文を定義できるようにする準備があるかどうかはわかりませんが、これを正しく解析してclangプロジェクトに追加する方法を開発する必要があることは明らかです。これ以上期待することは、clangプロジェクトに範囲外の何かを含めることです。

問題ない。一部の何もしないマクロ関数がこのタスクを処理できると思います。

私が説明していることを確認する別の方法は、言語自体で利用できるより限定されたツールを使用して実装する代わりに、ソースコードのAST(clangとそのAPIのおかげ)を操作することにより、ランタイムC ++を使用してメタプログラミング構成を実装することです。これには、明らかなコンパイルパフォーマンスの利点もあります(テンプレートが多いヘッダーでは、使用頻度に比例してコンパイルが遅くなります。コンパイルされたものの多くは、慎重に照合され、リンカーによって破棄されます)。

ただし、これには、ビルドプロセスに追加の手順を1つまたは2つ追加するコストと、ツールの一部として(確かに)多少冗長なソフトウェア(ただし、少なくとも単純なランタイムC ++)を記述する必要があります。 。

それは全体像ではありません。コア言語の機能では非常に困難または不可能であるコードを生成することで、はるかに大きな機能空間を確保できると確信しています。C ++ではテンプレート、マクロ、またはその両方のクレイジーな組み合わせを記述できますが、clangツールでは、セマンティックコンテンツへのフルアクセスを持ちながら、実行時に C ++で実現できる方法でクラスと関数を変更できます。テンプレートとマクロ、その他すべてに加えて。

それで、なぜ誰もがまだこれをしていないのかと思っています。clangのこの機能は非常に新しく、clangのASTの巨大なクラス階層に誰も精通していないのでしょうか?それはそれではありえない。

おそらく、私はこれの難しさを少し過小評価しているだけですが、clangツールを使用して「コンパイル時の文字列操作」を行うことは、犯罪的にほぼ単純です。冗長ですが、非常に単純です。必要なのは、実際の実際のstd::string操作にマップする一連のno-opマクロ関数だけです。clangプラグインは、関連するすべてのno-opマクロ呼び出しをフェッチしてこれを実装し、文字列を使用して操作を実行します。このツールは、ビルドプロセスの一部として挿入されます。ビルド中に、これらのno-opマクロ関数呼び出しは自動的にその結果に評価され、プログラム内の単純な古いコンパイル時文字列として挿入されます。その後、プログラムは通常どおりコンパイルできます。実際、結果として得られるこのプログラムは、結果としてはるかに移植性が高くなり、C ++ 11をサポートする豪華な新しいコンパイラーを必要としません。


これは異常に長い質問です。あなたはそれをあなたの最も適切なポイントに凝縮することができますか?
アモン

私は長い質問をたくさん投稿します。しかし、特にこれについては、質問のすべての部分が重要だと思います。たぶん最初の6段落をスキップしますか?はは。
Steven Lu

3
Lispで開拓され、最近Haxe、Nemerle、Scalaなどの言語で取り上げられた構文マクロによく似ています。Lispマクロが有害であると見なされる理由については、かなりの読みがあります。私はまだ説得力のある議論を聞いたことがありませんが、人々がそれらをすべての言語に追加することに消極的である理由を見つけるかもしれません(それが必ずしも簡単ではないという事実を除いて)。
back2dos 2014

ええ、そのメタC ++です。これは、より良い、より速いコードを意味します。これらの言語。さて、どこから始めましょうか。これらの言語で実装されている数百万ドル規模のビデオゲームとは何ですか?これらの言語で実装されている最新のWebブラウザーとは何ですか?OSカーネル?確かに、Haxeにはある程度の牽引力があるように見えますが、アイデアはわかります。
Steven Lu

1
@nwp、まあ、私はあなたが投稿の全体のポイントを逃したように見えることを指摘せざるを得ません。コンパイル時の文字列は、現在利用可能な機能の最も考案された最小限の具体例です。
Steven Lu

回答:


7

はい、バージニア州、サンタクロースがいます。

プログラムを使用してプログラムを変更するという考えは、ずっと前からあります。元のアイデアは、ストアドフォンコンピュータの形でジョンフォンノイマンから生まれました。しかし、任意の方法でマシンコードを変更するマシンコードは、かなり不便です。

人々は一般的にソースコードを変更したいと思っています。これは主にプログラム変換システム(PTS)の形で実現されます

PTSは通常、少なくとも1つのプログラミング言語に対して、ASTに解析し、そのASTを操作し、有効なソーステキストを再生成する機能を提供します。実際に調べてみると、ほとんどの主流言語について、誰かがそのようなツールを構築しています(ClangはC ++の例であり、Javaコンパイラはこの機能をAPIとして提供し、MicrosoftはRosyln、EclipseのJDTなどを提供しています)。実際にかなり便利なAPI。幅広いコミュニティにとって、ほとんどすべての言語固有のコミュニティは、さまざまな成熟度(通常は控えめで、多くの「ASTを生成するパーサー」)で実装された、このようなものを指すことができます。幸せなメタプログラミング。

[リフレクション指向のコミュニティがあり、プログラミング言語の内部からメタプログラミングを行おうとしていますが、「実行時」の動作の変更のみを実現し、言語コンパイラがリフレクションによっていくつかの情報を利用できる程度にしか行っていません。LISPを除き、リフレクションでは利用できないプログラムに関する詳細(「ルーク、ソースが必要」)が常にあり、リフレクションの機能を常に制限しています。]

より興味深いPTSは、任意の言語に対してこれを行います(ツールには、少なくともBNFを含む、構成パラメーターとして言語の説明を与えます)。このようなPTSを使用すると、「ソースからソースへの」変換を実行できます。たとえば、ターゲット言語の表面構文を使用してパターンを直接指定できます。このようなパターンを使用すると、目的のフラグメントをコード化したり、コードフラグメントを検索して置換したりできます。ほとんどの作業を行うためにASTに関するすべての微視的な詳細を知る必要がないため、これはプログラミングAPIよりもはるかに便利です。これをメタメタプログラミングと考えてください:-}

欠点:PTSがさまざまな種類の有用な静的分析(シンボルテーブル、制御およびデータフロー分析)を提供しない限り、ほとんどの実用的なタスクではタイプを確認して情報フローを確認する必要があるため、この方法で本当に興味深い変換を書くことは困難です。残念ながら、この機能は実際には一般的なPTSではまれです。(これは常に提案されている「パーサーがあったら...」では常に利用できません。

文字列の書き換えが可能であれば[ツリーの書き換えが可能]、任意の変換が可能であるという定理があります。したがって、多くのPTSはこれを利用して、ツリーの書き換えだけで何でもメタプログラミングできると主張しています。この定理は満足できるという意味では満足できるものですが、チューリングマシンが何かを実行できるというだけではチューリングマシンのプログラミングを選択する方法と同じではありません。(同じことが、手続き型APIだけのシステムにも当てはまります。ASTに任意の変更を加えることができる場合(そして実際、これはClangには当てはまらないと思います))。

必要なのは、言語パラメータ化されたタイプのPTS(複数の言語を処理する場合でも)の一般性を提供するシステムであり、追加の静的分析、ソースからソースへの変換と手続き型の変換を混合する機能を提供するシステムです。 API。これを行うのは2つだけです。

  • Rascal(MPL)メタプログラミング言語
  • DMSソフトウェアリエンジニアリングツールキット

自分で言語の説明と静的アナライザーを記述したくない場合(C ++の場合、これは膨大な量の作業であり、Clangがコンパイラーと一般的な手続き型メタプログラミングの基盤の両方として構築された理由です)、成熟した言語の説明を含むPTSが必要になりますすでに利用可能です。それ以外の場合は、PTSの構成にすべての時間を費やし、実際に実行したい作業を実行する人はいません。[ランダムで非主流の言語を選択した場合、このステップを回避するのは非常に困難です]。

Rascalはこれを "OPP"(Other People's Parsers)を併用することで試みますが、静的分析の部分では役に立ちません。彼らはJavaをかなり手に入れていると思いますが、CやC ++を実行しないと確信しています。しかし、それは学術研究ツールです。彼らを責めるのは難しい。

私が強調したいのは [商用] DMSツールにはJava、C、C ++のフルフロントエンドが利用可能であることです。C ++の場合は、GCCのC ++ 14のほぼすべてと、Microsoftのバリエーション(現在は改良中)、マクロの拡張と条件付き管理、メソッドレベルの制御とデータフロー分析をカバーしています。そして、はい、実用的な方法で文法の変更を指定できます。クライアント用にカスタムのVectorC ++システムを構築し、C ++を根本的に拡張して、F90 / APLデータ並列配列演算に相当する量を使用しました。DMSは、大規模なC ++システムで他の大規模なメタプログラミングタスク(たとえば、アプリケーションアーキテクチャの再形成)を実行するために使用されています。(私はDMSの設計者です)。

幸せなメタメタプログラミング。


クールですが、ClangとDMSは機能が重複していますが、実際には同じカテゴリに属していないソフトウェアです。つまり、1つはおそらく途方もなく高価であり、それにアクセスするために必要なリソースを正当化できない可能性があり、もう1つは制限のない無料のオープンソースです。これは大きな違いです...これらのエキサイティングなメタプログラミング機能について私をワクワクさせるのは、実際にそれを自由に使用するだけでなく、clangベースのバイナリツールを自由に配布することも許容されるという事実です。
Steven Lu

商業的に販売されているものは、無料のものと比較して「途方もなく高価」です。生のコストは問題ではありません。重要なことは、一部の人々にとって、商用製品を取得することによる見返りは、無料のアーティファクトに対する見返りよりも高いということです。それ以外の場合、商用ソフトウェアはありません。これは明らかにあなたの特定のニーズに依存します。Clangはツールスペースの興味深いポイントであり、確かに有用なアプリケーションのポイントがあります。(私はDMSアーキテクトであるため)DMSには幅広い機能があると思います。一例として、ClangがC ++以外の言語をサポートする可能性は低いです。
Ira Baxter

もちろん。DMSが信じられないほど強力で、ほぼ魔法のように(Arthur C. Clarkeのように)、clangはすばらしいものですが、実際にはよく記述されたC ++フロントエンドであり、その中には数多くあります。非常に多くの小さな前進、つまり、それをDMSと比較することはまだ公平ではありません。悲しいかな、私たちが自由に使える強力なツールがあったとしても、動作するソフトウェアはそれ自体を書きません。それは、ツールを使用した注意深い翻訳、または(ほとんどの場合常に優れたオプション)新しく記述されたものによって、まだ存在している必要があります。
Steven Lu

ClangやDMSなどのツールをフレッシュから構築する余裕はありません。また、5年以上かけて10人のチームで作成したアプリケーションを投入する余裕もありません。ソフトウェアのサイズと寿命が伸び続けるにつれて、そのようなツールがますます必要になるでしょう。
Ira Baxter

@StevenLu:さて、DMSはお世辞に感謝しますが、それについて魔法はありません。DMSには、ほぼ20年に及ぶエンジニアリングの利点と、かなりうまく機能しているクリーンなアーキテクチャプラットフォーム(aw、shucks、YMMV)のメリットがあります。同様に、Clangには優れたエンジニアリングがたくさんあります。私は同意します。これらはまったく同じ問題を解決するように設計されていません... DMSのスコープは、シンボリックプログラム操作に関しては大きく、実稼働コンパイラに関してははるかに小さくなるように明示的に意図されています。
Ira Baxter

4

(テンプレートを使用する代わりに)コンパイラーのAPIを使用したC ++でのメタプログラミングは確かに興味深いものであり、実際に可能です。メタプログラミングは(まだ)標準化されていないので、テンプレートの場合とは異なり、特定のコンパイラーに縛られることになります。

それで、なぜ誰もがまだこれをしていないのかと思っています。clangのこの機能は非常に新しく、clangのASTの巨大なクラス階層に誰も精通していないのでしょうか?それはそれではありえない。

多くの人がこれを行いますが、他の言語です。私の意見では、ほとんどのC ++(またはJava、C)開発者は、その必要性を(多分当然のことながら)認識していないか、メタプログラミングアプローチに慣れていません。また、IDEのリファクタリング/コード生成機能に満足していると思います。また、奇妙なものは複雑すぎる/保守が難しい/デバッグが難しいと思われるかもしれません。適切なツールがなければ、それは本当かもしれません。また、慣性や、人材の採用やトレーニングなどの技術以外の問題も考慮する必要があります。

ちなみに、Common Lispとそのマクロシステムについて言及しているので(Basileの回答を参照)、昨日、Claspがリリースされた(私は提携していません)と言う必要があります。

Claspは、LLVM IRにコンパイルするCommon Lispに準拠する実装になる予定です。さらに、Clangライブラリ(AST、Matcher)を開発者に公開します。

  • まず、これは、ライブラリを使用する場合を除いて、CLで記述してC ++を使用できなくなることを意味します(マクロが必要な場合はCLマクロを使用します)。

  • 次に、既存のC ++コード用のツールをCLで作成できます(分析、リファクタリングなど)。


3

いくつかのC ++コンパイラーは、多少なりとも文書化され、安定したAPIを備えています。特に、ほとんどのフリーソフトウェアコンパイラーです。

Clang / LLVMは主にライブラリの大きなセットであり、それらを使用できます。

最近のGCCプラグインを受け入れています。特に、MELT(それ自体がメタプラグインであり、GCCを拡張するための上位レベルのドメイン固有の言語を提供する)を使用してそれを拡張できます。

C ++ の構文はGCC(およびおそらくClangのどちらでも)で簡単に拡張できないことに注意してください。ただし、独自のプラグマ、ビルトイン、属性、およびコンパイラーパスを追加して、必要なことを行うことができます(おそらく、これらを呼び出すプリプロセッサーマクロも提供されます)。ユーザーフレンドリーな構文を提供します)。

多段階の言語とコンパイラに興味があるかもしれません。たとえば、C.CalcagnoらによるAST、Gensym、およびReflectionを使用した多段階言語の実装を参照してください。そして回避MetaOcamlCommon Lispのマクロ機能の内部を確認する必要があります。そして、あなたはによって興味がある可能性JITライブラリのようなlibjitGNU雷、さえLLVM、または単に-at実行時! -いくつかのC ++コードを生成し、共有オブジェクトの動的ライブラリにそれのコンパイルをforkし、その後のdlopen(3)共有されていることオブジェクト。J.Pitratのブログは、このような反射的なアプローチにも関連しています。また、RefPerSys


面白い。GCCがここで進化し続けるのを見るのはとても良いことです。これは私が尋ねたすべてに対処する答えではありませんが、それでも私はそれが好きです。
Steven Lu

再:あなたの新しい編集...それはコード自体を書き換えるのに良い点です。これは、実際にそのようなメタプログラム機能をC ++にももたらし始めており、以前よりもはるかにアクセスしやすく、これも非常に興味深いものです。
Steven Lu
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.