ライブラリ:boost.hana boost.PFR
https://www.cnblogs.com/zengkefu/p/6724323.html
C ++でのリフレクションの概要
2017年4月13日
これを聞いたことがあるなら止めてください。あなたは、メッセージングミドルウェア、ゲームエンジン、UIライブラリ、または増え続けるオブジェクトの数を処理する必要のあるその他の大規模なソフトウェアプロジェクトに取り組んでいます。これらのオブジェクトにはさまざまな品質がありますが、機能ごとにグループ化できます。ネットワークを介して送信したり、衝突したり、レンダリングしたりできます。
あなたはDRYの原則を信じる優れたプログラマーなので 、これらのオブジェクトを繰り返し処理する「アクション」コードを記述 し、特定のメッセージタイプまたはレンダリング可能なタイプを汎用パイプラインの適切な場所にプラグインします。オブジェクトを階層的に構成すると非常に便利です。たとえば、複数の異なるレンダリング可能な長方形で構成されるウィジェットクラスがある場合、構成要素の形状の既存のレンダリングロジックに基づいて、ウィジェットのレンダリングコードを自動的に生成できるようにしたいと考えています。 。
C ++では、これは思ったより難しいです。まず、継承と、「アクション」コードの統一されたインターフェイスを提供する親タイプを使用します。ただし、インターフェイスの互換性とスライスに関する問題がすぐに発生します。アプリケーションにとってレイテンシやコードサイズが重要な場合は、仮想関数によってパフォーマンスが低下します。
これらの種類のプロジェクトでは、外部コード生成ツール(メッセージエンコーディング用のProtobufやUIウィジェット用のQTメタオブジェクトコンパイラなど)を使用するのが一般的であると聞いています。このアプローチは特定のユースケースでは機能する可能性がありますが 、脆弱、拡張不可能、煩わしい、冗長など、いくつかの理由で間違っていると感じ ます。
C ++の最新かつ最高の機能に触れているので、関連する型へのジェネリックインターフェイスを提供するための継承の欠点を支援するConceptsと呼ばれるこのことについて耳にします。ただし、概念を実装するコンパイラを使用している場合でも、「Serialiazable」または「Renderable」の概念を導入しても、基本的な問題の一部は解決されません。オブジェクトを階層的に構成したい場合でも、タイプのメンバーのいずれかがその概念に準拠していることを自動的に検出する良い方法はありません。
これは新しい問題ではありません。2003年頃のEricS.RaymondによるTheArt of Unix Programmingからのこの抜粋を参照してください。この抜粋 では、この問題がfetchmailconf
プログラムに現れたときに説明されて います。
著者は、3つのクラスすべての構造を明示的に認識し、その知識を使用して初期化子を調べて一致するオブジェクトを作成する接着剤レイヤーを作成することを検討しましたが、新しいクラスメンバーが構成言語として時間の経過とともに追加される可能性があるため、そのアイデアを拒否しました新しい機能が増えました。オブジェクト作成コードが明白な方法で記述されている場合、それは壊れやすく、クラス定義が変更されたときに同期が外れる傾向があります。繰り返しますが、無限のバグのレシピです。
より良い方法は、データ駆動プログラミングです。初期化子の形状とメンバーを分析し、クラス定義自体にメンバーについてクエリを実行してから、2つのセットのインピーダンスを整合させるコードです。
Lispプログラマーはこれを内省と呼んでいます。オブジェクト指向言語では、メタクラスハッキングと呼ばれ、一般に恐ろしく難解で深遠な黒魔術と見なされ ます。
この記事で使用するこの難解なアートには、別の名前があります。それはリフレクションです。おそらく、C ++にはすでに黒魔術が多すぎると思うでしょう。しかし、今日の言語、特にコンパイル時の内省を反映する必要があること、そしてそれは恐ろしい、難解な謎ではなく、コードベースをよりクリーンで効果的にすることができる便利なツールであることを納得させたいと思います。
これまでの話
深く掘り下げる前に、いくつかの用語を定義しましょう。以前のブログ投稿からわかるように、名前を特定するのはイライラするほど難しいので、実際のコンテンツにたどり着くことができるように、定義についてあまり混乱させないようにしましょう。
イントロスペクション は、タイプを検査し、そのさまざまな品質を取得する機能です。オブジェクトのデータメンバー、メンバー関数、継承階層などを内省したい場合があります。また、コンパイル時と実行時にさまざまなものを内省したい場合があります。
メタオブジェクト は、タイプのイントロスペクションの結果です。イントロスペクションから要求したメタデータを含むハンドルです。リフレクションの実装が適切な場合、このメタオブジェクトハンドルは、実行時に軽量またはゼロコストである必要があります。
具象化 とは、「何かを一流の市民にする」、または「何かを具体的にする」という意味のファンシーな言葉です。これを使用して、オブジェクト(メタオブジェクト)の反映された表現から具体的なタイプまたは識別子へのマッピングを意味します。
実際、C ++ 98またはC ++ 14のどちらを使用していても、今日の言語での反映を実現する方法はいくつかあります。これらの方法には長所と短所がありますが、全体的な基本的な問題は、導入部で説明したユースケースを解決する標準化された言語レベルのリフレクション機能がないことです。
RTTI
実行時型情報/識別は物議を醸すC ++機能であり、パフォーマンスマニアやオーバーヘッドゼロの熱狂者によって一般的に罵倒されます。あなたが今まで使用している場合 dynamic_cast
、 typeid
または type_info
、あなたはRTTIを使用していました。
RTTIは、実行時に型間の関係を照合するためのC ++の組み込みメカニズムです。その関係は、平等または継承による関係である可能性があります。したがって、一種の限定されたランタイムイントロスペクションを実装します。C ++ RTTIでは、仮想関数をランタイムで決定された実装にディスパッチするために使用されるポインターの配列であるvtables(仮想関数テーブル)を使用するすべてのクラスに対して、追加のランタイム状態を生成する必要があります。コンパイル -fnortti
は、この余分な状態の生成を無効にするための一般的な最適化です。これにより、コードサイズとコンパイル時間を節約できます(コンパイラーの作業が少ないため)。ただし、ランタイムポリモーフィズムを実現する「慣用的な」C ++の方法も無効になります。
ちょっとした補足:より良い選択肢があるのに、なぜRTTIの慣用的なC ++なのですか?LLVMは、vtableのないクラスで機能するRTTIの代替を実装しますが、少し余分なボイラープレートが必要です(LLVMプログラマーズマニュアルを参照)。また、この手法で使用される追加の定型文に目を細めると、これを汎用ライブラリで形式化し、動的な概念(他の言語ではインターフェイスと呼ばれることもあります)などのアイデアを追加することも想像できます。実際、Louis Dionneは、まさにそれを行う実験的なライブラリに取り組んで います。
リフレクションメカニズムとして、RTTIは標準化されており、3つの主要なコンパイラ(Clang、gcc、MSVC)のいずれかで使用できるという事実以外に、多くの利点はありません。これは、これから説明する他のリフレクションメカニズムほど強力ではなく、上記の問題ステートメントに適合しません。それでも、ランタイム情報からコンパイル時タイプへのマッピングの一般的な問題は、多くの興味深いアプリケーションにとって重要です。
マクロ
この言語ではほとんど何でもできるので、C ++は好き嫌い です。マクロは、C ++のフットガンの兵器庫の中で最も柔軟なツールです。
代わりにテンプレートを使用して実行できるマクロで実行できることはたくさんあります。これにより、型の安全性、より優れたエラー処理、および多くの場合、マクロよりも優れた最適化が可能になります。ただし、リフレクションはテンプレートだけでは実現できず、マクロを活用する必要があります(次のセクションで説明するように、C ++ 17まで)。
Boost C ++ライブラリは、MPL、Fusion、Hanaなど、テンプレートメタプログラミング用のユーティリティライブラリをいくつか提供しています。FusionとHanaはどちらも、ユーザー定義のPODタイプを、タプルのようにアクセスできる内省可能な構造に適合させることができるマクロを提供します。私はライブラリに精通していて、新しいライブラリであるため、Hanaに焦点を当てます。ただし、Fusionが提供するマクロのインターフェースは同じです。
Hanaのドキュメントに は、これらのマクロの完全な概要が記載されています。内省可能な構造体をBOOST_HANA_DEFINE_STRUCT
次のように定義することができます :
struct Person {
BOOST_HANA_DEFINE_STRUCT(Person,
(std::string, name),
(std::string, last_name),
(int, age)
);
};
または、別のライブラリで定義されている型を内省したい場合は、次を使用できます BOOST_HANA_ADAPT_STRUCT
。
BOOST_HANA_ADAPT_STRUCT(Person, name, last_name, age);
これを行った後、一般的な方法で構造体にアクセスできます。たとえば、次を使用して構造体のフィールドを反復処理できます hana::for_each
。
Person jackie{"Jackie", "Kay", 24};
hana::for_each(jackie, [](auto pair) {
std::cout << hana::to<char const*>(hana::first(pair)) << ": "
<< hana::second(pair) << std::endl;
});
// name: Jackie
// last_name: Kay
// age: 24
魔法の背後にある基本的な考え方は、マクロが各メンバーの汎用セットおよび取得関数を生成することです。マクロに指定されたフィールド名を「文字列化」して、constexpr文字列キーとして使用できるようにし、識別子名とタイプを使用して各フィールドのメンバーポインタを取得します。次に、構造体をコンパイル時の文字列キーとメンバーポインタアクセサのペアのタプルに適合させます。
それはかなり簡単に聞こえますが、コードは非常に手ごわくて冗長です。これは、特定のサイズの構造体を適応させるための別個のマクロケースがあるためです。たとえば、1つのメンバーを持つすべての構造体は特定のマクロにマップされ、2つのメンバーを持つすべての構造体は次のマクロにマップされます。
マクロは、実際には200行未満のRubyテンプレートから生成され ます。Rubyを知らないかもしれませんが、テンプレートを見るとパターンを味わうことができます。テンプレートは最大n = 62まで拡張できます(改行は私自身のものです)。
#define BOOST_HANA_DEFINE_STRUCT(...) \
BOOST_HANA_DEFINE_STRUCT_IMPL(BOOST_HANA_PP_NARG(__VA_ARGS__), __VA_ARGS__)
#define BOOST_HANA_DEFINE_STRUCT_IMPL(N, ...) \
BOOST_HANA_PP_CONCAT(BOOST_HANA_DEFINE_STRUCT_IMPL_, N)(__VA_ARGS__)
<% (0..MAX_NUMBER_OF_MEMBERS).each do |n| %>
#define BOOST_HANA_DEFINE_STRUCT_IMPL_<%= n+1 %>(TYPE <%= (1..n).map { |i| ", m#{i}" }.join %>) \
<%= (1..n).map { |i| "BOOST_HANA_PP_DROP_BACK m#{i} BOOST_HANA_PP_BACK m#{i};" }.join(' ') %> \
\
struct hana_accessors_impl { \
static constexpr auto apply() { \
struct member_names { \
static constexpr auto get() { \
return ::boost::hana::make_tuple( \
<%= (1..n).map { |i| \
"BOOST_HANA_PP_STRINGIZE(BOOST_HANA_PP_BACK m#{i})" }.join(', ') %> \
); \
} \
}; \
return ::boost::hana::make_tuple( \
<%= (1..n).map { |i| "::boost::hana::make_pair( \
::boost::hana::struct_detail::prepare_member_name<#{i-1}, member_names>(), \
::boost::hana::struct_detail::member_ptr< \
decltype(&TYPE::BOOST_HANA_PP_BACK m#{i}), \
&TYPE::BOOST_HANA_PP_BACK m#{i}>{})" }.join(', ') %> \
); \
} \
} \
なぜこれらのライブラリの作者はそもそもこれらのマクロを書いたのですか?もちろん自慢する権利!
実際、構造体へのタプルのようなアクセスは、ジェネリックプログラミングに非常に役立ちます。これにより、各メンバーの特定の名前を知らなくても、特定の概念に準拠するタイプのすべてのメンバーまたはメンバーのサブセットに対して操作を実行できます。繰り返しますが、これは継承、テンプレート、および概念だけでは得られない機能です。
これらのマクロの主な欠点は、型を反映するにはDEFINE_STRUCT
、で定義するか、型とそのすべての内省可能なメンバーをADAPT_STRUCT
。で正しい順序で参照する必要があることです。このソリューションでは、コード生成手法よりも論理的およびコードの冗長性が少なくて済みますが、言語レベルの反映があれば、この最後の手作業によるハッキングは必要ありません。
magic_get
C ++ 14でのPODイントロスペクションの現在の頂点は、magic_get
AntonyPolukhinによって呼び出されたライブラリ です。FusionやHanaのアプローチとは異なり 、イントロスペクトする構造体ごとに または マクロをmagic_get
呼び出す必要はありません 。代わりに、任意のPODタイプのオブジェクトを ユーティリティ関数に入れて、タプルのようにアクセスできます。例えば:DEFINE_STRUCT
ADAPT_STRUCT
magic_get
Person author{"Jackie", "Kay", 24};
std::cout << "People are made up of " << boost::pfr::tuple_size<Person>::value << " things.\n"
std::cout << boost::pfr::get<0>(author) << " has a lot to say about reflection\n"!
// People are made up of 3 things.
// Jackie has a lot to say about reflection!
もう1つのエキサイティングな点 magic_get
は、マクロを使用せず、テンプレートと新しい構造化バインディング機能のみを使用するC ++ 17実装を提供することです。
Antonyがこれをどのように達成するかを確認するために、内部を覗いてみましょう 。
template <class T>
constexpr auto as_tuple_impl(T&& /*val*/, size_t_<0>) noexcept {
return sequence_tuple::tuple<>{};
}
template <class T>
constexpr auto as_tuple_impl(T&& val, size_t_<1>) noexcept {
auto& [a] = std::forward<T>(val);
return ::boost::pfr::detail::make_tuple_of_references(a);
}
template <class T>
constexpr auto as_tuple_impl(T&& val, size_t_<2>) noexcept {
auto& [a,b] = std::forward<T>(val);
return ::boost::pfr::detail::make_tuple_of_references(a,b);
}
これらの as_tuple_impl
スペシャライゼーションは、この同じパターンを繰り返して、合計101のスペシャライゼーションを作成します(一部の自由は改行で取得されました)。
template <class T>
constexpr auto as_tuple_impl(T&& val, size_t_<100>) noexcept {
auto& [
a,b,c,d,e,f,g,h,j,k,l,m,n,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,J,K,L,M,N,
P,Q,R,S,T,U,V,W,X,Y,Z,
aa,ab,ac,ad,ae,af,ag,ah,aj,ak,al,am,an,ap,aq,ar,as,at,au,av,aw,ax,ay,az,
aA,aB,aC,aD,aE,aF,aG,aH,aJ,aK,aL,aM,aN,aP,aQ,aR,aS,aT,aU,aV,aW,aX,aY,aZ,
ba,bb,bc,bd
] = std::forward<T>(val);
return ::boost::pfr::detail::make_tuple_of_references(
a,b,c,d,e,f,g,h,j,k,l,m,n,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,J,K,L,M,N,
P,Q,R,S,T,U,V,W,X,Y,Z,
aa,ab,ac,ad,ae,af,ag,ah,aj,ak,al,am,an,ap,aq,ar,as,at,au,av,aw,ax,ay,az,aA,
aB,aC,aD,aE,aF,aG,aH,aJ,aK,aL,aM,aN,aP,aQ,aR,aS,aT,aU,aV,aW,aX,aY,aZ,
ba,bb,bc,bd
);
}
何が起きてる?Antonyは、タイプTのフラットな構造体を、アルファベットの連続する文字にちなんで名付けられた一連の参照に分解しています。次に、これらのフィールドをカスタムタプルタイプの参照のタプルに転送します。これは、構造体からタプルに「変換」するためのコアメカニズムです。ただし、任意の数のフィールドを非構造化する方法(「可変個引数構造化バインディング」?)がないため、このコードを一般的な方法で記述することはできず、代わりにライブラリはPythonスクリプトを介してこれらの特殊化を生成し ます。
繰り返しますが、C ++で言語レベルのリフレクションがあれば、これらのコード生成手法は必要ありません。
コンパイラツール
Clang C ++コンパイラは、ソースコードを取得して実行可能ファイルを吐き出すスタンドアロンの実行可能ファイルではありません。 解析されたC ++コードのASTへのインターフェイスを使用して、libclang
または LibTooling
独自のツールを作成でき ます。イントロスペクションAPIはコンパイラーが使用するすべての情報を使用できるため、独自のASTを理解するコードは、リフレクションを使用するだけでなく、最も強力な形式のイントロスペクションにアクセスします。
siplasplas
はManuSanchezによって作成されたリフレクションエンジンで、libclangを使用してC ++タイプのメタオブジェクトヘッダーを生成し、静的および動的リフレクションを有効にします。
このアプローチの欠点は、特定のコンパイラのAPIによってロックインされることです。別のコンパイラのツールの互換性レイヤーを作成したい場合でも、GCCとMSVCはClangのようにASTアクセス用の類似のモジュラーライブラリを提供していないため、おそらくできません。
他の言語での反映
C ++プログラマーは通常、他のプログラミングコミュニティを軽蔑することがありますが、言語機能について判断を下す前に、他の言語でどのように行われるかを理解することが重要だと思います。これは、言語を改善しようとしている委員会に参加している言語設計者から、別の言語に特化し、比較によって他の場所に移動することを選択できる日常のプログラマーまで、すべての人に当てはまります。
Eric S. Raymondからの以前の引用では、Lispでの内省について言及しています。Unixの世界で一般的な他の言語を比較し続けます。
ほとんどのオブジェクト指向言語はそれをまったくサポートしていません[リフレクション]。そうするもの(Perlが1つ)では、それは複雑で壊れやすい作業になる傾向があります。メタクラスハッキングのためのPythonの機能は非常にアクセスしやすいです。」
ただし、他のいくつかの主流の汎用プログラミング言語がさまざまな程度の能力を備えた反射機能を提供しているため、The Art ofUnixプログラミングはその時代を示しています。
Python
ESRが言うように、Pythonリフレクションインターフェースはユーザーフレンドリーで柔軟性がありますが、これにはパフォーマンスが犠牲になります。type
、などのオブジェクトのメタデータをクエリするためのユーティリティ関数がいくつかあります hasattr
。また、dir
引数が指定されていない場合、またはオブジェクトの名前が指定されている場合はオブジェクトの属性のリストを返す、現在のスコープ内のすべての名前を一覧表示する非常に強力な コマンドがあります。 。dir
モジュールで使用して、特定のモジュールがインポートする関数とオブジェクトを確認することもでき ます。setattr
オブジェクトに新しい属性を追加delattr
したり、属性を削除したり するために使用でき ます。
私のお気に入りのPythonライブラリの1つは BeautifulSoupです。これは、Pythonリフレクション機能を多用するHTML / XMLパーサーです。Beautiful Soupを使用してドキュメントを解析すると、結果のオブジェクトのXMLタグと属性がPython属性に直接変換されます。たとえば、次のようなXMLツリーです。
<html>
<head>
<title>
The Dormouse's story
</title>
</head>
…ドキュメントがsoup
:と呼ばれるオブジェクトに解析される場合、このようにアクセスできます 。
soup.title.string
# u'The Dormouse's story'
soup.title.parent.name
# u'head'
このアクセスは、任意のXMLタグに対して機能します(XMLが整形式である限り)。
このようなライブラリをC ++で実装することは、ランタイム値をメンバー識別子に変換する必要があるため、基本的に不可能です。リフレクションがパーサーライブラリに最適であるとまだ確信していない場合は、今日のC ++で最も強力なパーサーライブラリはBoostSpiritであり、BoostFusionリフレクションマクロを多用していると考えてください。
Java
JavaリフレクションAPIは(Pythonとは異なり)高度に構造化されており、非常に広範囲です。これは、言語構造を表すクラスの一連の定義と、それらの言語構造に関する情報を抽出するための関数を提供します。
これは、このチュートリアルから採用された、名前のリストからクラスオブジェクトを作成する例です 。
public static void main(String... args) {
Class<?> c = Class.forName(args[0]);
out.format("Class:%n %s%n%n", c.getCanonicalName());
Package p = c.getPackage();
out.format("Package:%n %s%n%n",
(p != null ? p.getName() : "-- No Package --"));
for (int i = 1; i < args.length; i++) {
switch (ClassMember.valueOf(args[i])) {
case CONSTRUCTOR:
printMembers(c.getConstructors(), "Constructor");
break;
case FIELD:
printMembers(c.getFields(), "Fields");
break;
case METHOD:
printMembers(c.getMethods(), "Methods");
break;
case CLASS:
printClasses(c);
break;
case ALL:
printMembers(c.getConstructors(), "Constuctors");
printMembers(c.getFields(), "Fields");
printMembers(c.getMethods(), "Methods");
printClasses(c);
break;
default:
assert false;
}
}
}
ご覧のとおり、リフレクションの結果として生じるオブジェクトは単純なランタイム構造であるため、Javaリフレクションにはランタイムコストが発生します。これは、C ++コミュニティのパフォーマンスフリークの多くには受け入れられません。実行時にリフレクションデータが必要ない場合、これはゼロコストの抽象化ではありません。
佳作
C#リフレクションシステムはJavaのシステムとやや似ています。文字列識別子による型ルックアップを可能にし、型をランタイムオブジェクトとして具体化します。反映されたフィールドを取得および設定し、反映された関数を呼び出すためのユーティリティがあります。これは、「ランタイムアセンブリ」のユースケースを念頭に置いて設計されました。つまり、実行時に動的共有ライブラリをロードし、リフレクションインターフェイスを使用して共有ライブラリのAPIにアクセスする機能です。詳細については、これらの 記事をお勧め します。
Goは、よりシンプルで「生産性の高い」C ++の代替となるように設計されました。より大きな標準ライブラリのように、C ++に比べていくつかの利点がありますが、ジェネリックスとメタプログラミングのための機能は、最新のC ++とはかけ離れています。Goがジェネリックを必要としない理由に関するこの(今ややや時代遅れの)記事は 、 ジェネリックプログラミング(私のような)が好きならあなたを怒らせるかもしれませんが、言語レベルのジェネリックの代用であるインターフェースとリフレクションの紹介もします。Go reflect
パッケージは、ランタイムリフレクションを実装します。これは基本的に、インターフェイスに格納されている具象型のランタイム識別子を取得するためのユーティリティです。これに興味がある場合は、Goの反射の法則について読んでください 。
Rustは、多くの分野でC ++の注目すべき競争相手です。C ++と同様に、現在、言語レベルのリフレクションサポートがありませんが、ASTプラグイン、属性、およびマクロの組み合わせを使用して実装をハッキングすることは可能です。
標準化への道:reflexprとoperator $
C ++ ISO標準化委員会には、リフレクションとメタプログラミングに関する研究グループSG7があり、C ++ 20を対象とした複数のリフレクション提案を評価しています。
reflexpr
MatúšChochlík、アクセルナウマン、とDavid Sankelの提案は、新しいタイプのを渡すことによってアクセスされているいくつかの「メタオブジェクト」紹介 reflexpr
演算子を。 デザインの概要については「一言で言えば 静的反射」 を、より長い概要については静的反射をお勧めし ます。MatúšのClangフォークを実装する手順を見つけ reflexpr
、彼のドキュメントを読み、彼のmirror
ユーティリティライブラリを ここで探索でき ます。
AndrewSuttonとHerbSutter は、クラスや名前空間などからオブジェクトメタデータを取得する方法として、リフレクション演算子を導入した「静的リフレクションの設計」を作成しました $
(使用$
は一般的であるため、好意的ではないと主張されています。 レガシーコード、特に必ずしも有効なC ++ではないがC ++ソースを生成するコード生成およびテンプレートシステム。)ここで、提案を実装するAndrewSuttonのClangフォークを調べることができ ます。
これら2つの論文の基本的な設計上の違いは、反射演算子の結果が値であるかタイプであるかです。
たとえば、これはreflexpr
紙を使った簡単な反射の例 です。
namespace meta = std::meta;
using MetaPerson = reflexpr(Person);
std::cout << "A " << meta::get_base_name_v<MetaPerson> << " is made up of "
<< meta::get_size<MetaPerson> << " things.\n";
// A Person is made up of 3 things.
そして、これがあなたが使用して同じことをする方法です operator$
:
namespace meta = cpp3k::meta::v1;
constexpr auto info = $Person;
std::cout << "A " << info.name() << " is made up of
" << info.member_variables().size() << " things.\n";
タイプベースのメタプログラミングと値ベースのメタプログラミングの背後にある完全な議論は、それ自体が別の長いブログ投稿になる可能性があります。実際、リフレクションはメタプログラミング機能のユースケースであり、言語でリフレクションを標準化したいという願望が、C ++メタプログラミングの背後にある設計上の問題をより明確にしています。C ++メタプログラミングの現在の状態では、コンパイラが純粋な型の演算を計算する方が、constexpr値の演算を計算するよりも速いことがよくあります。OdinHolmesの作業とブログ投稿およびベンチマークを参照してください Metal、Hana、およびKvasirMPLライブラリを比較します。ただし、Boost Hanaのような値ベースのメタプログラミングの構文は、完全にテンプレートの山かっこ内で作業して宣言を使用するよりも直感的で簡潔です。また、値ベースのメタプログラミングスタイルで、コンパイル時の値から実行時の値にジャンプする方が簡単です。
私の意見では、言語設計は値ベースのメタプログラミングの方向に進むべきであり、コンパイラーの実装者は、constexprを多用するコードベースのコンパイル速度をどのように改善できるかを評価する必要があります。これにより、平均的なユーザーにとってメタプログラミングがはるかにアクセスしやすく実用的になります。ただし、標準ではコンパイル時の要件を強制することはできません。これは完全に実装の品質の問題であり、コンパイル時間をどれだけ改善できるかを評価するのに十分なコンパイラについては知りませんが、コンパイラの作成者が世界で最もパフォーマンス指向のプログラマの一部であることは知っています。
タイプベースのリフレクションAPIは、値ベースのメタプログラミングユーティリティにラップできることにも注意してください。このポイントは、「一言で言えば静的リフレクション」の論文で明示的に強調されています。今日の言語仕様とコンパイラ実装の状態を考えると、純粋な型の土地に留まりたい人のために最小限の型ベースのリフレクション機能を提供し、価値の利便性を好む人を許可することは安全なアプローチのようですユーティリティライブラリを使用するためのベースのメタプログラミング。ただし、言語が古いスタイルのテンプレートメタプログラミングから急速に移行している場合、このアプローチは将来を見据えたものではないと主張することができます。これらの点は、具体的な例と開発者の経験がなければ議論するのは難しいですが、
タイプベースのメタプログラミングと値ベースのメタプログラミングのどちらを好むかに関係なく、静的リフレクションをより快適にするためにC ++標準に追加する必要のあるメタプログラミング機能がいくつかあります。フィールドとタイプの名前を使用した計算には、標準化された表現constexpr文字列が必要です。C ++ 17は私たちに if constexpr
二項演算子を使用して式を折りたたむが、constexpr制御フローをconstexpr forループに拡張し、より表現力豊かで一般的な折り畳みユーティリティは、静的リフレクションで実行したい種類の作業に非常に役立ちます。そして、コンセプトは、TSにあり、まだ言語の第一級市民ではないにもかかわらず、両方のリフレクションペーパーの設計の前提条件です。これらのポイントのいくつかがまだ完全に明確でない場合は、このシリーズの次の記事で例を挙げて説明します。
結論(今のところ)
リフレクションは、多くの異なるタイプとそれらのタイプ間の複雑な関係を処理する汎用ライブラリと巨大なフレームワークにとって不可欠なツールであると私は信じています。C ++でリフレクションを標準化すると、一般的なライブラリエコシステムを強化することで、平均的なプログラマーがこの機能にアクセスしやすくなり、C ++プログラマーの全体的な生産性が向上します。オーバーヘッドゼロのリフレクション機能を提供すると、C ++は、Javaのようにリフレクションを提供する他の汎用の主流プログラミング言語を改善し、Rustのようなリフレクション機能のない競合言語と区別するのにも役立ちます。
次回は、このシリーズのパート2に注目してください。ここでは、C ++でのリフレクションで何ができるかを示します。