web-dev-qa-db-ja.com

テンプレートメタプログラミングを使用して階乗を計算する

このコードがどのように機能するのかわかりません (ウィキペディアから)

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
    int x = Factorial<4>::value; // == 24
    int y = Factorial<0>::value; // == 1
}
  • <int N>を取るこの奇妙なテンプレートは何ですか?
  • この2番目の奇妙なテンプレート<>は何ですか?
  • enumerationsは何のためのものですか?
  • 通常の実行時階乗計算ではなく、これを使用する利点は何ですか?
  • どのくらいの頻度でこれを使用しますか?私はしばらくの間C++を使用していますが、これまで使用したことはありません。 C++のどのくらいの部分を見逃していましたか?

ありがとう!

22
Lazer
  • <int N>を取るこの奇妙なテンプレートは何ですか?

C++では、テンプレート引数は型(接頭辞classまたはtypename)または整数(接頭辞intまたはunsigned int)のいずれかです。これが2番目のケースです。

  • この2番目の奇妙なtemplate <>は何ですか?

template<> struct Factorial<0>は、Factorialクラステンプレートの完全な特殊化です。つまり、0は、Factorialの独自のバージョンに対応する特別な値と見なされます。

  • 列挙型は何ですか?

列挙型は、C++のメタプログラミングで値を計算する方法です。

  • 通常の実行時階乗計算ではなく、これを使用する利点は何ですか?

このコードが最初に作成された理由は、メタプログラミングを使用して微積分を実行できるという概念実証を作成するためです。利点は、生成されたコードが非常に効率的であることです(Factorial<4>::valueを呼び出すことは、コードに単に「24」を書き込むことと同じです。

  • どのくらいの頻度でこれを使用しますか?私はしばらくの間C++を使用していますが、これまで使用したことはありません。 C++のどのくらいの部分を見逃していましたか?

このような機能がこの方法で実現されることはめったにありませんが、最近ではメタプログラミングがますます使用されています。何ができるかのヒントを得るには、 Boost meta-programming library を参照してください。

26
Benoît

<int N>を取るこの奇妙なテンプレートは何ですか?

テンプレート宣言は、クラス/タイプ、値、およびポインターに対して行うことができます。テンプレートの定義でテンプレートパラメータに定数が使用されることはめったにないため、通常、このフォームは表示されません。

この2番目の奇妙なテンプレート<>は何ですか?

テンプレートを考える最良の方法は、コンパイラーが行うのと同様のことを行うことです。テンプレートをクラスと見なし、テンプレートパラメーターをそのタイプの実際の値に置き換えます。

つまり、次の場合です。

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

Factorial<2>::valueは次と同等です。

// generated by the compiler when Factorial<2>::value is encountered in code:
struct Factorial2 { enum { value = 2 * Factorial1::value }; };
struct Factorial1 { enum { value = 1 * Factorial0::value }; };
// not generated by compiler, as it was explicitly defined in the code you ask about:
template <> struct Factorial<0> { enum { value = 1 }; }; // defines Factorial<0>

列挙型は何ですか?

これらは、コンパイル時にconst valueを使用して列挙型を定義し、例のようなクライアントコードを記述できるようにします。

通常の実行時階乗計算ではなく、これを使用する利点は何ですか?

利点は効率です。値の計算はコンパイル時に拡張されるため、Factorial<10>::valueの実行時コストはFactorial <1> :: valueと同じです。コンパイルされたコードでは、これらは両方とも定数です。パフォーマンスが重要なコードで階乗を計算する場合、コンパイル時にそれがどの値であるかがわかっている場合は、これを行うか、オフラインで計算して、コードで事前に計算された値を使用して定数を定義する必要があります。

どのくらいの頻度でこれを使用しますか?

あなたが参照する「これ」は定数の再帰的計算ですか?もしそうなら、頻繁ではありません。

「これ」はテンプレートの定数の定義ですか?よく :)

「これ」は特定のタイプのテンプレートの特殊化ですか?非常に非常に頻繁に。 std :: vectorには、一部のSTL実装で完全に別個の実装があることを理解しました(8つの要素を持つstd::vector<bool>は、値を格納するために1バイトを超える必要はありません)。

私はしばらくの間C++を使用していますが、これまで使用したことはありません。 C++のどのくらいの部分を見逃していましたか?

必ずしも大きな部分ではありません。ブーストを使用する場合、使用するコードはこのようなものを使用して実装されている可能性があります。

6
utnapistim

この回答 by Johannes Schaub --litb 別の質問で非常に役に立ちました。

[ヨハネスがそれで大丈夫だと仮定して、私はここに答えをコピーして貼り付けています。彼が気に入らなければペーストを取り除きます]


はい、それは非型パラメーターです。いくつかの種類のテンプレートパラメータを持つことができます

  • タイプパラメータ。
    • タイプ
    • テンプレート(クラスのみ、関数なし)
  • 非型パラメータ
    • ポインタ
    • 参考文献
    • 積分定数式

あなたが持っているものは最後の種類のものです。これはコンパイル時定数(いわゆる定数式)であり、整数型または列挙型です。標準で調べた後、テンプレートは型ではありませんが、クラステンプレートを型セクションに移動する必要がありました。ただし、これらの種類を説明するために、タイプパラメータと呼ばれます。ポインター(およびメンバーポインター)と、外部リンクを持つオブジェクト/関数(他のオブジェクトファイルからリンクでき、プログラム全体でアドレスが一意であるもの)への参照を持つことができます。例:

テンプレートタイプパラメータ:

template<typename T>
struct Container {
    T t;
};

// pass type "long" as argument.
Container<long> test;

テンプレート整数パラメーター:

template<unsigned int S>
struct Vector {
    unsigned char bytes[S];
};

// pass 3 as argument.
Vector<3> test;

テンプレートポインタパラメータ(関数へのポインタを渡す)

template<void (*F)()>
struct FunctionWrapper {
    static void call_it() { F(); }
};

// pass address of function do_it as argument.
void do_it() { }
FunctionWrapper<&do_it> test;

テンプレート参照パラメーター(整数を渡す)

template<int &A>
struct SillyExample {
    static void do_it() { A = 10; }
};

// pass flag as argument
int flag;
SillyExample<flag> test;

テンプレートテンプレートパラメータ。

template<template<typename T> class AllocatePolicy>
struct Pool {
    void allocate(size_t n) {
        int *p = AllocatePolicy<int>::allocate(n);
    }
};

// pass the template "allocator" as argument. 
template<typename T>
struct allocator { static T * allocate(size_t n) { return 0; } };
Pool<allocator> test;

パラメータのないテンプレートは使用できません。ただし、明示的な引数のないテンプレートも可能です。デフォルトの引数があります。

template<unsigned int SIZE = 3>
struct Vector {
    unsigned char buffer[SIZE];
};

Vector<> test;

構文的には、template<>は、パラメーターのないテンプレートではなく、明示的なテンプレートの特殊化をマークするために予約されています。

template<>
struct Vector<3> {
    // alternative definition for SIZE == 3
};

4
Lazer

これはコンパイラによって実行される再帰です。

template <>
struct Factorial<0>
{
}

再帰の出口点です。

最初にFactorial<4>が解決されます。値4にはFactorialの特殊化がないため、最初の定義Factorial<>が使用されます。次に、その値はFactorial<3>::valueで計算され、前のステップが繰り返されます。

これは、N == 1になるまで続き、N-1の場合、特殊化Factorial<0>が機能し、値が1に設定されます。再帰はここで停止し、コンパイラーは上に移動して残りを計算できます。 Factorial<N>の値。

3
jopa

通常の実行時階乗計算ではなく、これを使用する利点は何ですか?

より高速です。理論的には、コンパイラはコンパイル時にテンプレートのセットを拡張して、Factorial<n>::valueが単純に単一の定数になるようにします。いくつかの非常に少数のケースでは、それはかなりの違いを生むかもしれません。

欠点は、コンパイル時に計算する必要があることです。そのため、実行時にnが決定されると、そのメソッドを使用できなくなります。

どのくらいの頻度でこれを使用しますか?私はしばらくの間C++を使用していますが、これまで使用したことはありません。 C++のどのくらいの部分を見逃していましたか?

そのような場合、ほとんどありません。テンプレートメタプログラミングは潜在的に非常に強力ですが、この例が有用であり、パフォーマンスにとって十分に重要であるケースの数はごくわずかです。

ただし、C++では、言語が他の方法では手の届かない強力なトリックを多数実行できるため、便利な手法です。他の誰かがあなたのために行ったテンプレートメタプログラミングを利用する方がはるかに一般的だと思います。 Boostはそれを非常に頻繁に使用し、結果としていくつかの非常に巧妙なことを行うことができます。これは強力ですが、うまく実行されないとコードが難読化される可能性もあります。

3
Peter