Concept TS による任意計算の実行

先週の火曜日、私は Concept TS を詳しく調べました。これは、TMP の代替に関する概念の力と有用性についての議論に続きました (@irrequietus と @Manu343726 に叫びます)。そのため、概念をサポートする GCC トランクをコンパイルした後、任意の計算を行うために概念だけを使用する方法を具体的に調べました。注意:これはまったく無意味です。そのために、与えられた数が素数かどうかをチェックする Prime_number の概念を実装しようとしました。

先週の火曜日、私は概念 TS を詳しく調べました。これは、TMP の代替に関する概念の力と有用性についての議論に続きました (@irrequietus と @Manu343726 に叫ぶ)。したがって、概念をサポートする GCC トランクをコンパイルした後、任意の計算を行うために概念だけを使用する方法を具体的に調べました.

そのために、 00 を実装しようとしました 与えられた数が素数であるかどうかをチェックする概念。概念と 10 のみを使用する必要があります 計算を行います。

まあ、私は成功しました... ある程度 .

コンセプト バージョンを紹介する前に、少し時間をさかのぼって説明します。各ポイントで、素数チェックを実装するためのコンパイル時プログラミングの方法を見ていきます。

C++14 constexpr ソリューション

C++14 は非常に強力な 29 を提供します 、したがって、基本的には単純な CS 101 ソリューションであり、32 だけです。 前面:

constexpr bool is_prime_number(int i)
{
 if (i == 1)
 return false;
 else if (i == 2)
 return true;
 else if (i % 2 == 0)
 return false;
 for (auto div = 3; div * div <= i; div += 2)
 if (i % div == 0)
 return false;
 return true;
}

しかし、それは単純すぎる.誰もがこのようなコードを書くことができます.

それでは、C++11 に戻りましょう。

C++11 constexpr

C++11 の 42 はループを許可しないため、再帰を介して実行する必要があります。そのために、除数の検索を別の関数に抽出しました:

constexpr bool is_prime_number_helper(int i, int div)
{
 return div * div <= i ? (i % div == 0 ? false : is_prime_number_helper(i, div + 2)) : true;
}

constexpr bool is_prime_number(int i)
{
 return i == 2 ? true : (i == 1 || i % 2 == 0 ? false : is_prime_number_helper(i, 3));
}

この実装は気に入っています。エレガントでコンパクトです。

56 の 2 つの条件がどのようになっているかに注意してください。 内側のループの条件と外側のループの終了に対応します。また、60 で条件をどのように並べ替えたかにも注目してください。 2 つの些細な 75 をグループ化する

しかし、さらにさかのぼってみましょう。

C++98 メタプログラミング

86より前の時間を思い出してください ?テンプレートの特殊化を介してコンパイル時の計算を行う必要があった場所はどこですか?

さて、ここにいます:

template <int I, int Div, int Rest>
struct is_prime_number_helper // I % Div != 0
{
 enum {value = is_prime_number_helper<I, Div + 2, I % (Div + 2)>::value};
};

template <int I, int Div>
struct is_prime_number_helper<I, Div, 0> // I % Div == 0
{
 enum {value = false};
};

template <int I>
struct is_prime_number_helper<I, I, 0> // I == Div
{
 enum {value = true};
};

template <int I, bool Even>
struct is_prime_number_nontrivial;

template <int I>
struct is_prime_number_nontrivial<I, true> // I even
{
 enum {value = false};
};

template <int I>
struct is_prime_number_nontrivial<I, false> // I not even
{
 enum {value = is_prime_number_helper<I, 3, I % 3>::value};
};

template <int I>
struct is_prime_number // general case
{
 enum {value = is_prime_number_nontrivial<I, I % 2 == 0>::value};
};

template <>
struct is_prime_number<1> // special case 1
{
 enum {value = false};
};

template <>
struct is_prime_number<2> // special case 2
{
 enum {value = true};
};

コンパイラができるだけ早くインスタンス化を停止できるように、多くのテンプレートの特殊化を慎重に作成しました。除数チェックは 95 まで実行されることに注意してください。 、 107 に特化する簡単な方法はありません .

そして今、私たちは 18 年先にジャンプして同じコードを書きますが、クラス テンプレートの代わりに概念を使用しています。

コンセプト

概念については既に聞いたことがあると思います。

118 任意の 121 を取ることができます 値なので、135 を書きます 概念は非常に簡単です:

template <int I>
concept bool Prime_number = is_prime_number(I);

これが、任意の計算に概念を使用する方法です。読んでくれてありがとう。

ええ、でもそれは不正行為です。

計算には概念のみを使用したいと明示的に述べました.

全体的な戦略は、C++98 ソリューションと非常によく似ています。ブランチは 145 によって実装されます。 、テンプレートの特殊化ではなく、構文は異なりますが、手法は基本的に同じです。

前と同じように、まず 157 除数チェックを行います:

// Div * Div > I
template <int I, int Div> requires Div * Div > I
concept bool Prime_number_helper()
{
 return true;
}

// I % Div == 0
template <int I, int Div> requires Div * Div <= I && I % Div == 0
concept bool Prime_number_helper()
{
 return false;
}

// I % Div != 0
template <int I, int Div> requires Div * Div <= I && I % Div != 0
concept bool Prime_number_helper()
{
 return Prime_number_helper<I, Div + 2>(); 
}

この部分を 3 つの条件に分割する必要があることに注意してください。すべてを 1 つにまとめ、169 を使用します。 コンパイラが計算しようとすると、演算子は無限再帰につながります。

そして 178 コンセプトはとても簡単です:

template <int I> requires I <= 1
concept bool Prime_number()
{
 return false;
}

template <int I> requires I == 2
concept bool Prime_number()
{
 return true;
}

template <int I> requires I > 2 && I % 2 == 0
concept bool Prime_number()
{
 return false;
}

template <int I> requires I > 2 && I % 2 == 1
concept bool Prime_number()
{
 return Prime_number_helper<I, 3>();
}

すべてのオーバーロードに分離条件があることに注意する必要があるだけです。そうしないと、オーバーロードされた関数へのあいまいな呼び出しが発生します。 エラーです。

更新:

このコードは、184 を防止する特別な規則により、実際には形式が正しくありません 197 で まさにその理由です。 ただし、「通常の」200 として記述することもできます 関数、つまり 214 と書く 228 の代わりに したがって、実際には 237 を使用して任意のコンパイル時の計算を行うことができます 、245 ではありません . それでも:無意味だけどかっこいい

これは何に役立つのですか?

役に立ちません。

まったく無意味です。

最先端のテクノロジーを使用して、1998 年と同じ方法で何かを作成しました。

でも、今日は楽しい午後でした。

そして、C++ の機能がおそらく意図したよりもはるかに多くのことを実行できることをもう一度証明します。 /263 答えは出ませんが、それだけで強力な計算が可能になります。

より複雑で美しく、実際に機能する - GCC が修正するまで - バージョンはこちら