テンプレート メタプログラミング - 仕組み

前回の投稿「テンプレート メタプログラミング - すべての始まり」では、テンプレート メタプログラミングのルーツについて書き、テンプレート メタプログラミングのこんにちは世界、つまりコンパイル時に数値の階乗を計算することを紹介しました。この投稿では、テンプレート メタプログラミングを使用してコンパイル時に型を変更する方法について書きます。

前回の投稿「テンプレート メタプログラミング - すべての始まり」の階乗プログラムは良い例でしたが、テンプレートのメタプログラミングでは慣用的ではありません。テンプレートのメタプログラミングでは、コンパイル時に型を操作するのが一般的です。

コンパイル時の型操作

たとえば、std::move が概念的に行っていることは次のとおりです。

static_cast<std::remove_reference<decltype(arg)>::type&&>(arg);

std::move 引数 arg を取ります 、そのタイプ (decltype(arg)) を推測します 、その参照を削除します (std::remove_reverence )、右辺値参照 (static_cast<...>::type&&>) にキャストします。 )。基本的に、
std::move 右辺値参照キャストです。これで、移動セマンティクスが開始されます。

関数はどのようにして引数から constness を取り除くことができますか?

// removeConst.cpp

#include <iostream>
#include <type_traits>

template<typename T >
 struct removeConst {
 using type = T; // (1)
};

template<typename T >
 struct removeConst<const T> {
 using type = T; // (2)
};

int main() {

 std::cout << std::boolalpha;
 std::cout << std::is_same<int, removeConst<int>::type>::value << '\n'; // true 
 std::cout << std::is_same<int, removeConst<const int>::type>::value << '\n'; // true

}

removeConst を実装しました 道 std::remove_const おそらく型特性ライブラリに実装されています。 std::is_same 型特性ライブラリから、両方の型が同じかどうかをコンパイル時に判断するのに役立ちます。 removeConst<int>の場合 プライマリまたは一般クラスのテンプレートが開始されます。 removeConst<const int>の場合 、 const T の部分的な特殊化 適用されます。重要な観察は、両方のクラス テンプレートがエイリアス type を介して (1) と (2) の基になる型を返すことです。 .約束どおり、引数の constness は削除されます。


追加の観察事項があります:

  • テンプレートの特殊化 (部分的または完全) は、コンパイル時の条件付き実行です。もっと具体的に言ってみましょう:removeConst を使用する場合 非定数の int 、コンパイラはプライマリまたは一般的なテンプレートを選択します。定数 int を使用する場合 、コンパイラは const T の部分的な特殊化を選択します .
  • type = Tを使った表現 この場合は型である戻り値として機能します。
  • C++ Insights でプログラム removeConst.cpp を調べると、std::is_same<int, removeConst<int>::type>::value という式が ブール値 std::integral_constant<bool, true>::value に要約されます true と表示されます .

一歩下がって、より概念的な見方のためにテンプレート メタプログラミングについて書きましょう。

その他のメタ

実行時には、データと関数を使用します。コンパイル時に、メタデータとメタ関数を使用します。論理的には、メタプログラミングを行うため、メタと呼ばれます。

メタデータ

メタデータは、コンパイル時にメタ関数となる値です。

値には次の 3 種類があります。

  • int、double などの型
  • C++20 での整数、列挙子、ポインタ、参照、浮動小数点などの非型
  • std::vector などのテンプレート 、または std::deque

3 種類の値の詳細については、以前の記事「エイリアス テンプレートとテンプレート パラメーター」を参照してください。

メタ関数

メタ関数は、コンパイル時に実行される関数です。

確かに、これは奇妙に聞こえます。型は、関数をシミュレートするためにテンプレート メタプログラミングで使用されます。メタ関数の定義に基づくと、constexpr コンパイル時に実行できる関数もメタ関数です。 consteval についても同様です C++20 の関数。

ここに 2 つのメタ関数があります。

template <int a , int b>
struct Product {
 static int const value = a * b;
};

template<typename T >
struct removeConst<const T> {
 using type = T;
};

最初のメタ関数 Product 値と 2 番目の removeConst を返します タイプを返します。名前の値と型は、戻り値の命名規則にすぎません。メタ関数が値を返す場合、それは値と呼ばれます。型を返す場合、それは型と呼ばれます。型特性ライブラリは、まさにこの命名規則に従います。

関数をメタ関数と比較することは非常に啓発的です。

関数とメタ関数

次の関数 power およびメタ関数 Power 実行時とコンパイル時に pow(2, 10) を計算します。

// power.cpp

#include <iostream>

int power(int m, int n) { 
 int r = 1;
 for(int k = 1; k <= n; ++k) r *= m;
 return r; 
}

template<int m, int n> 
struct Power {
 static int const value = m * Power<m, n-1>::value;
};
 
template<int m> 
struct Power<m, 0> { 
 static int const value = 1; 
};

int main() {
 
 std::cout << '\n'; 
 
 std::cout << "power(2, 10)= " << power(2, 10) << '\n';
 std::cout << "Power<2,10>::value= " << Power<2, 10>::value << '\n';
 
 std::cout << '\n';
}

これが主な違いです:

  • 引数 :関数の引数は丸括弧 (( ... )) に入り、メタ関数の引数は鋭角括弧 (< ...>) に入ります。 )。この観察は、関数とメタ関数の定義にも当てはまります。関数は丸かっこを使用し、メタ関数は鋭角かっこを使用します。各メタ関数引数は新しい型を生成します。
  • 戻り値 :関数は return ステートメントを使用し、メタ関数は静的な整数定数値を使用します。

この比較については、constexpr に関する次の投稿で詳しく説明します。 と consteval 機能。これがプログラムの出力です。

power 実行時に実行され、Power しかし、次の例では何が起こっているのでしょうか?

// powerHybrid.cpp

#include <iostream>

template<int n>
int Power(int m){
 return m * Power<n-1>(m);
}

template<>
int Power<0>(int m){
 return 1;
}

int main() {
 
 std::cout << '\n';

 std::cout << "Power<0>(10): " << Power<0>(20) << '\n';
 std::cout << "Power<1>(10): " << Power<1>(10) << '\n';
 std::cout << "Power<2>(10): " << Power<2>(10) << '\n';
 

 std::cout << '\n';

}

質問は明らかに:Power です 関数またはメタ関数?この質問への回答により、より多くの洞察が得られることをお約束します。

次は?

次の投稿では、関数/メタ関数 Power を分析します 型特性ライブラリを紹介します。型特性ライブラリは、C++ でのコンパイル時プログラミングに適しています。