この質問を参照して、メタプログラミングのコード例を説明して投稿してもらえますか? この用語をグーグルで検索しましたが、実際に使用できると確信させる例は見つかりませんでした.
また、Qt のメタ オブジェクト システムはメタプログラミングの一種ですか?
ジュニア
この質問を参照して、メタプログラミングのコード例を説明して投稿してもらえますか? この用語をグーグルで検索しましたが、実際に使用できると確信させる例は見つかりませんでした.
また、Qt のメタ オブジェクト システムはメタプログラミングの一種ですか?
ジュニア
これまでの例のほとんどは、値(円周率の数字、Nの階乗など)を操作しており、それらはほとんど教科書の例ですが、一般的にはあまり役に立ちません。円周率の17桁目を計算するためにコンパイラが本当に必要な状況を想像するのは難しいです。自分でハードコーディングするか、実行時に計算します。
現実の世界により関連性があるかもしれない例はこれであるかもしれません:
サイズがテンプレートパラメータである配列クラスがあるとしましょう(したがって、これは10個の整数の配列を宣言しますarray<int, 10>
:)
ここで、2つの配列を連結したい場合があります。少しのメタプログラミングを使用して、結果の配列サイズを計算できます。
template <typename T, int lhs_size, int rhs_size>
array<T, lhs_size + rhs_size> concat(const array<T, lhs_size>& lhs, const array<T, rhs_size>& rhs){
array<T, lhs_size + rhs_size> result;
// copy values from lhs and rhs to result
return result;
}
非常に単純な例ですが、少なくともタイプにはある種の現実世界の関連性があります。この関数は、正しいサイズの配列を生成します。これは、コンパイル時に、完全な型の安全性を備えて行われます。そして、値をハードコーディングする(さまざまなサイズの配列を多数連結したい場合がある)か、実行時に(型情報が失われるため)、簡単に実行できなかった処理を計算しています。
ただし、より一般的には、値ではなく型にメタプログラミングを使用する傾向があります。
良い例が標準ライブラリにあるかもしれません。各コンテナタイプは独自のイテレータタイプを定義しますが、単純な古いポインタをイテレータとして使用することもできます。技術的には、イテレータは、などの多くのtypedefメンバーを公開する必要がありvalue_type
、ポインタは明らかにそれを行いません。したがって、メタプログラミングを少し使用して、「ああ、しかし、イテレータ型がポインタであることが判明した場合は、value_type
代わりにこの定義を使用する必要があります」と言います。
これについて注意すべきことが2つあります。1つ目は、値ではなく型を操作しているということです。「Nの階乗はそうだ」と言っているのではなく、「value_type
T型の階乗は...として定義されている」と言っています。
2つ目は、ジェネリックプログラミングを容易にするために使用されることです。(イテレータは、すべての例の中で最も単純な配列へのポインタで機能しない場合、非常に一般的な概念ではありません。したがって、ポインタが有効であると見なされるために必要な詳細を入力するために、メタプログラミングを少し使用します。イテレータ)。
これは、メタプログラミングのかなり一般的なユースケースです。もちろん、他のさまざまな目的に使用できます(式テンプレートは、高価な計算を最適化することを目的とした、もう1つの一般的に使用される例であり、Boost.Spiritは、完全にオーバーボードして、コンパイル時に独自のパーサーを定義できるようにする例です。時間)ですが、おそらく最も一般的な使用法は、特別な処理を必要とし、ジェネリックプログラミングを不可能にするこれらの小さなバンプやコーナーケースを滑らかにすることです。
この概念は完全にメタという名前から来ており、接頭辞が付いているものから抽象化することを意味します。
物自体ではなく、物に対して何かをするためのより「会話的なスタイル」。
この点で、メタプログラミングは本質的にコードを書くことであり、それはより多くのコードを書く (または書かれる原因となる)。
C++ テンプレート システムは、(c プリプロセッサが行うように) 単純にテキスト置換を行うのではなく、解析するコード構造とやり取りしてはるかに複雑なコードを出力する (複雑で非効率的な) 手段を備えているため、メタ プログラミングです。この点で、C++ でのテンプレートの前処理はチューリング完全です。これは何かがメタプログラミングであると言う必要条件ではありませんが、そのように数えられるにはほぼ確実に十分です。
パラメータ化可能なコード生成ツールは、テンプレート ロジックが十分に複雑な場合、メタプログラミングと見なすことができます。
システムが言語を表す抽象構文ツリー (言語を表現するテキスト形式とは対照的に) に近づくほど、メタプログラミングと見なされる可能性が高くなります。
QT MetaObjects コードを見ると、C++ テンプレート システムや Lisp マクロのようなもののために通常予約されている意味で、(ざっと調べた限りでは) それをメタ プログラミングとは呼びません。これは単に、コンパイル段階で既存のクラスにいくつかの機能を注入するコード生成の形式のように見えます (現在流行している一種のアスペクト指向プログラミング スタイルや、JavaScript などの言語のプロトタイプ ベースのオブジェクト システムの前身と見なすことができます)。
C++ でこれを取得できる極端な長さの例として、Boost MPLがあり、そのチュートリアルでは取得方法が示されています。
ディメンション タイプ(測定単位)
quantity<float,length> l( 1.0f );
quantity<float,mass> m( 2.0f );
m = l; // compile-time type error
二回 (f, x) := f(f(x))
template <class F, class X>
struct twice
: apply1<F, typename apply1<F,X>::type>
{};
struct add_pointer_f
{
template <class T>
struct apply : boost::add_pointer<T> {};
};
これで、add_pointer_f を 2 回使用して、ポインターからポインターへのポインターを作成できます。
BOOST_STATIC_ASSERT((
boost::is_same<
twice<add_pointer_f, int>::type
, int**
>::value
));
大きい (2000loc) ですが、コンパイラに依存せず、オブジェクトのマーシャリングとメタデータを含み、ストレージのオーバーヘッドやアクセス時間のペナルティがない、c++ 内の再帰クラス システムを作成しました。これは筋金入りのメタプログラミングであり、非常に大規模なオンライン ゲームで、ネットワーク転送とデータベース マッピング (ORM) のためにゲーム オブジェクトをマッピングするために使用されています。
いずれにせよ、コンパイルには約 5 分かかりますが、オブジェクトごとに手動で調整されたコードと同じくらい高速であるという利点があります。そのため、サーバーの CPU 時間を大幅に削減することで、多くの費用を節約できます (CPU 使用率は以前の 5% です)。
一般的な例は次のとおりです。
template <int N>
struct fact {
enum { value = N * fact<N-1>::value };
};
template <>
struct fact<1> {
enum { value = 1 };
};
std::cout << "5! = " << fact<5>::value << std::endl;
基本的に、階乗を計算するためにテンプレートを使用しています。
最近見たより実用的な例は、テンプレートクラスを使用して基になるテーブルの外部キー関係をモデル化するDBテーブルに基づくオブジェクトモデルでした。
別の例: この場合、ガウス・ルジャンドル アルゴリズムを使用して、コンパイル時に PI の任意精度の値を取得するために、メタプログラミング手法が使用されます。
現実の世界でそのようなものを使用する必要があるのはなぜですか? たとえば、計算の繰り返しを避けるため、より小さな実行可能ファイルを取得するため、特定のアーキテクチャでパフォーマンスを最大化するためにコードを調整するため、...
個人的にはメタプログラミングが大好きです。同じことを繰り返すのが嫌いで、アーキテクチャの制限を利用して定数を調整できるからです。
気に入っていただければ幸いです。
ちょうど私の2セント。
/**
* FILE : MetaPI.cpp
* COMPILE : g++ -Wall -Winline -pedantic -O1 MetaPI.cpp -o MetaPI
* CHECK : g++ -Wall -Winline -pedantic -O1 -S -c MetaPI.cpp [read file MetaPI.s]
* PURPOSE : simple example template metaprogramming to compute the
* value of PI using [1,2].
*
* TESTED ON:
* - Windows XP, x86 32-bit, G++ 4.3.3
*
* REFERENCES:
* [1]: http://en.wikipedia.org/wiki/Gauss%E2%80%93Legendre_algorithm
* [2]: http://www.geocities.com/hjsmithh/Pi/Gauss_L.html
* [3]: http://ubiety.uwaterloo.ca/~tveldhui/papers/Template-Metaprograms/meta-art.html
*
* NOTE: to make assembly code more human-readable, we'll avoid using
* C++ standard includes/libraries. Instead we'll use C's ones.
*/
#include <cmath>
#include <cstdio>
template <int maxIterations>
inline static double compute(double &a, double &b, double &t, double &p)
{
double y = a;
a = (a + b) / 2;
b = sqrt(b * y);
t = t - p * ((y - a) * (y - a));
p = 2 * p;
return compute<maxIterations - 1>(a, b, t, p);
}
// template specialization: used to stop the template instantiation
// recursion and to return the final value (pi) computed by Gauss-Legendre algorithm
template <>
inline double compute<0>(double &a, double &b, double &t, double &p)
{
return ((a + b) * (a + b)) / (4 * t);
}
template <int maxIterations>
inline static double compute()
{
double a = 1;
double b = (double)1 / sqrt(2.0);
double t = (double)1 / 4;
double p = 1;
return compute<maxIterations>(a, b, t, p); // call the overloaded function
}
int main(int argc, char **argv)
{
printf("\nTEMPLATE METAPROGRAMMING EXAMPLE:\n");
printf("Compile-time PI computation based on\n");
printf("Gauss-Legendre algorithm (C++)\n\n");
printf("Pi=%.16f\n\n", compute<5>());
return 0;
}
次の例は、優れた本C++Templates-完全ガイドから抜粋したものです。
#include <iostream>
using namespace std;
template <int N> struct Pow3 {
enum { pow = 3 * Pow3<N-1>::pow };
}
template <> struct Pow3<0> {
enum { pow = 1 };
}
int main() {
cout << "3 to the 7 is " << Pow<7>::pow << "\n";
}
このコードのポイントは、3の7乗の再帰計算が、実行時ではなくコンパイル時に行われることです。したがって、コンパイルが遅くなる代わりに、実行時のパフォーマンスの点で非常に効率的です。
これは役に立ちますか?この例では、おそらくそうではありません。ただし、コンパイル時に計算を実行すると有利になる場合があるという問題があります。
C++メタプログラミングが何であるかを言うのは難しいです。関数型プログラミングのように、変数として「型」を導入するのとよく似ていると私はますます感じています。これにより、C++で宣言型プログラミングが可能になります。
例を示す方がはるかに簡単です。
私のお気に入りの1つは、複数のネストされたswitch/case
ブロックをフラット化するための「トリック」(またはパターン:))です。
#include <iostream>
using namespace std;
enum CCountry { Belgium, Japan };
enum CEra { ancient, medieval, future };
// nested switch
void historic( CCountry country, CEra era ) {
switch( country ) {
case( Belgium ):
switch( era ) {
case( ancient ): cout << "Ambiorix"; break;
case( medieval ): cout << "Keizer Karel"; break;
}
break;
case( Japan ):
switch( era ) {
case( future ): cout << "another Ruby?"; break;
case( medieval ): cout << "Musashi Mijamoto"; break;
}
break;
}
}
// the flattened, metaprogramming way
// define the conversion from 'runtime arguments' to compile-time arguments (if needed...)
// or use just as is.
template< CCountry country, CEra era > void thistoric();
template<> void thistoric<Belgium, ancient> () { cout << "Ambiorix"; }
template<> void thistoric<Belgium, medieval>() { cout << "Keizer Karel"; }
template<> void thistoric<Belgium, future >() { cout << "Beer, lots of it"; }
template<> void thistoric<Japan, ancient> () { cout << "wikipedia"; }
template<> void thistoric<Japan, medieval>() { cout << "Musashi"; }
template<> void thistoric<Japan, future >() { cout << "another Ruby?"; }
// optional: conversion from runtime to compile-time
//
template< CCountry country > struct SelectCountry {
static void select( CEra era ) {
switch (era) {
case( medieval ): thistoric<country, medieval>(); break;
case( ancient ): thistoric<country, ancient >(); break;
case( future ): thistoric<country, future >(); break;
}
}
};
void Thistoric ( CCountry country, CEra era ) {
switch( country ) {
case( Belgium ): SelectCountry<Belgium>::select( era ); break;
case( Japan ): SelectCountry<Japan >::select( era ); break;
}
}
int main() {
historic( Belgium, medieval ); // plain, nested switch
thistoric<Belgium,medieval>(); // direct compile time switch
Thistoric( Belgium, medieval );// flattened nested switch
return 0;
}
boost::variant
日常業務でBoost.MPLを使用する必要があるのは、との間で変換する必要があるときだけでしたQVariant
。
boost::variant
O(1)訪問メカニズムがあるため、toboost::variant
方向QVariant
はほとんど重要ではありません。
ただし、QVariant
訪問メカニズムがないため、に変換するには、特定のインスタンス化が保持できるタイプboost::variant
を繰り返し処理する必要があります。タイプごとに、そのタイプが含まれているかどうかを確認します。含まれている場合は、値を抽出し、で返します。それはとても楽しいです、あなたはそれを試してみるべきです:)mpl::list
boost::variant
QVariant
boost::variant
QtMetaObject は基本的にリフレクション ( Reflection ) を実装しており、 ISはメタプログラミングの主要な形式の 1 つであり、実際には非常に強力です。これは Java のリフレクションに似ており、動的言語 (Python、Ruby、PHP...) でも一般的に使用されています。テンプレートより読みやすいですが、どちらにも長所と短所があります。
これは、階乗に沿った単純な「値の計算」です。ただし、コードで実際に使用する可能性がはるかに高いものです。
マクロ CT_NEXTPOWEROFTWO2(VAL) は、テンプレート メタプログラミングを使用して、コンパイル時に既知の値以上の次の 2 の累乗を計算します。
template<long long int POW2VAL> class NextPow2Helper
{
enum { c_ValueMinusOneBit = (POW2VAL&(POW2VAL-1)) };
public:
enum {
c_TopBit = (c_ValueMinusOneBit) ?
NextPow2Helper<c_ValueMinusOneBit>::c_TopBit : POW2VAL,
c_Pow2ThatIsGreaterOrEqual = (c_ValueMinusOneBit) ?
(c_TopBit<<1) : c_TopBit
};
};
template<> class NextPow2Helper<1>
{ public: enum { c_TopBit = 1, c_Pow2ThatIsGreaterOrEqual = 1 }; };
template<> class NextPow2Helper<0>
{ public: enum { c_TopBit = 0, c_Pow2ThatIsGreaterOrEqual = 0 }; };
// This only works for values known at Compile Time (CT)
#define CT_NEXTPOWEROFTWO2(VAL) NextPow2Helper<VAL>::c_Pow2ThatIsGreaterOrEqual