テンプレート メタプログラミング - すべての始まり

メタプログラミングは、プログラムのプログラミングです。 C++ は、コンパイル時にメタプログラミングを適用します。 C++98 でテンプレート メタプログラミングを使用して開始され、C++11 で型特性ライブラリを使用して形式化され、C++11 以降は着実に改善されています。主な原動力は定数式です。この投稿では、そのルーツについて書きたいと思います。

テンプレート メタプログラミングについて書く意図は、そのテクニックを分かりやすく説明することです。このわかりやすい説明は、型特性ライブラリの機能をよりよく理解するのに役立ち、特に constexpr を高く評価します。 .テンプレート メタプログラミングの悪い評判のほとんどは、膨大な長さのエラー メッセージが表示される可能性があることです。テンプレート メタプログラミングは設計されたものではなく、偶然に始まりました。

事故

1994 年、Siemens の Erwin Unruh は、C++ 委員会の会合で、コンパイルされないプログラムを発表しました。これはおそらく、コンパイルに成功しなかった最も有名なプログラムです。

// Prime number computation by Erwin Unruh
template <int i> struct D { D(void*); operator int(); };

template <int p, int i> struct is_prime {
 enum { prim = (p%i) && is_prime<(i > 2 ? p : 0), i -1> :: prim };
 };

template < int i > struct Prime_print {
 Prime_print<i-1> a;
 enum { prim = is_prime<i, i-1>::prim };
 void f() { D<i> d = prim; }
 };

struct is_prime<0,0> { enum {prim=1}; };
struct is_prime<0,1> { enum {prim=1}; };
struct Prime_print<2> { enum {prim = 1}; void f() { D<2> d = prim; } };
#ifndef LAST
#define LAST 10
#endif
main () {
 Prime_print<LAST> a;
 } 

Erwin Unruh は Metaware Compilers を使用しましたが、このプログラムは C++ では有効ではなくなりました。著者による新しいバリアントはこちらです。では、なぜこの番組が有名になったのでしょうか。 type を txpe と記述した元のエラー メッセージを見てみましょう。

重要な部分を赤でハイライトしました。パターンがわかると思います。プログラムは、コンパイル時に最初の素数を 30 まで計算します。これは、テンプレートのインスタンス化を使用して、コンパイル時に計算を行うことができることを意味します。それはさらに良いです。テンプレート メタプログラミングはチューリング完全であるため、あらゆる計算問題の解決に使用できます。 (もちろん、再帰インスタンス化の深さ (C++11 では少なくとも 1024) とテンプレートのインスタンス化中に生成される名前の長さにより、いくつかの制限が生じるため、チューリング完全性はテンプレート メタプログラミングの理論上のみ成立します。)

魔法の仕組み

何が起こっているのかを段階的に分解してみましょう。

コンパイル時の計算

数値の階乗を計算することは、テンプレート メタプログラミングの「Hello World」です。

// factorial.cpp

#include <iostream>

template <int N> // (2)
struct Factorial{
 static int const value = N * Factorial<N-1>::value;
};

template <> // (3)
struct Factorial<1>{
 static int const value = 1;
};

int main(){
 
 std::cout << '\n';
 
 std::cout << "Factorial<5>::value: " << Factorial<5>::value << '\n'; // (1)
 std::cout << "Factorial<10>::value: " << Factorial<10>::value << '\n'; // (4)
 
 std::cout << '\n';

}

呼び出し factorial<5>::value 行 (1) では、行 (2) でプライマリ テンプレートまたは一般テンプレートがインスタンス化されます。このインスタンス化の間、Factorial<4>::value インスタンス化されます。この再帰は、完全に特殊化されたクラス テンプレート Factorial<1> の場合に終了します。 ラインでキックします(3)。たぶん、あなたはそれがより絵のようなものを好むでしょう.

プログラムの出力は次のとおりです。

C++ Insights と Compiler Explorer のおかげで、プログラムをさらに分析することができます。これは、テンプレートのインスタンス化とテンプレートのメタプログラミングに関する直感を構築するのに役立ちます。

C++ インサイトから始めましょう:

C++ インサイト

コール Factorial<5>::value (行 1) は、5 から 2 までの数字のクラス テンプレートのインスタンス化を引き起こします。1 の完全な特殊化は既に利用可能です。呼び出し Factorial<10>::value (行 2) 10 から 6 の数字の関数テンプレートのインスタンス化を引き起こします。これは、他のすべての完全に特殊化された関数テンプレートが既に使用可能であるためです。次の出力は、5 から 2 までの数値のインスタンス化を示しています。

さて、私の分析は Compiler Explorer で続けられます。

コンパイラ エクスプローラ

簡単にするために、メイン プログラムと対応するアセンブラー命令のスクリーンショットのみを提供します。

Compiler Explorer を使用すると、このコンパイル時の計算を視覚化できます。

出力はそれを示しています。 5 と 10 の階乗は単なる定数であり、コンパイル時に計算されました。アセンブラ命令の最初の行と最後の行で結果を直接確認できます。

CppCon 2021

今週は、以前の投稿をこの投稿の出発点として使用できることを非常にうれしく思います。今週、私は CppCon で 4 つの講演を行いましたが、正直言って多すぎました。 Youtube の CppCon チャンネルで公開されている私の講演は次のとおりです。 PDF は既に利用可能です。

  • 同時実行パターン
  • 最新の C++ における Constness のさまざまな特徴
  • オブジェクト指向プログラミング:良い部分
  • C++20:小さな真珠

次は?

次回の投稿では、テンプレート メタプログラミングの旅を続け、より多くの洞察を提供します。