constexpr はプラットフォームです

役立つ洞察を共有させてください:08 はプラットフォームです。

Windows またはマイクロコントローラーを対象とするコードを作成するのと同じように、コンパイル時の実行を対象とするコードを作成します。どちらの場合も、ターゲット プラットフォームで動作する C++ のサブセットに制限し、コードを移植する必要がある場合は条件付きコンパイルを使用します。 、目的のターゲット プラットフォームで実行します。したがって、13 を表示できます。 ターゲットにすることができる別のプラットフォームとして、たまたまコンパイラによって実行されます。

この洞察は、28 を取り巻く多くの設計上の質問に答えることができます .

30 であってはならないもの ?

コンパイル時プログラミングの機能は、C++ のすべてのバージョンで拡張されており、標準ライブラリのますます多くの関数が 43 とマークされています。 .これは疑問を投げかけます:59 であってはならないもの ?

62 を扱いましょう プラットフォームとして、マイクロコントローラと比較してみてください。どの C++ 関数をそれに移植できますか?ここでの答えはもっと単純です。まず、OS とのインターフェイスを持たない移植可能な C++ はすべて動作します。一部の OS でさえもです。機能を実装できます:70 に出力 チップに適切なハードウェアなどがある場合は、ある種のデバッグ出力になる可能性があります。シングルコア プロセッサでのスレッド化やシステムでのウィンドウ作成など、他の API は実行できないか、意味がありません。したがって、プラットフォームでは、移植可能な C++ コードと、システムが提供する API の上に構築できるすべてのものを使用できます。

同じことが 81 にも当てはまります :移植可能なすべてのもの、標準 C++ は、システム API の上に構築されたすべての機能と同様に、コンパイル時に使用できる必要があります。ここでの「システム」は、診断を発行するためのインターフェイスを提供できるコンパイラです。 96 との大きな違いの 1 つ プラットフォームと従来のものは 103 です 関数は、グローバル (実行時) 状態とまったくやり取りできません。

したがって、(C++17/20 以降の) C++ ライブラリを使用している場合、副作用や OS との相互作用のないすべての関数は 113 であると期待するのが合理的です。

もちろん、ライブラリ作成者が実際に 122 にする必要があると判断したかどうかは、 は別の質問です.結局のところ、コンパイル時のプログラミングは現在、単純なものか、より難解なライブラリに限定されているため、多くの需要はありません.

関数を 135 としてマークする必要がありますか? ?

現在、関数 145 を明示的にマークする必要があります 159 の場合 ただし、これが必要とされない将来のバージョンの C++ を想像することもできます。コンパイル時に関数を呼び出す場合、コンパイラはコンパイル時にそれを実行しようとします。診断を発行します。そうすれば、すべてを手動で 163 としてマークする必要がなくなります。 、これは不必要なボイラープレートです。

それとも?

178 を想像してみましょう 関数には必要なく、関数 189 を提供するライブラリがあります :

int get_the_answer()
{
    int result;
    /* expensive computation */;
    return result;
}

たまたま 198 200 です であるため、ユーザーはコンパイル時にそれを使用します。

constexpr int the_answer = lib::get_the_answer();

次に、ライブラリの作成者は 211 を最適化したい 224 をキャッシュすることにより :

int get_the_answer_impl() { /* as before */ }

int get_the_answer()
{
    // Lazily compute once.
    static int result = get_the_answer_impl();
    return result;
}

これは重大な変更です:238 関数に 248 を含めることはできません 変数!ユーザーのコードが壊れています。これが、250 を明示的にマークする必要がある理由です。 262 の関数 .そうすることで、コンパイル時に使用できる関数を文書化し、それをユーザーに約束します。

しかし、271 を比較してみましょう 別のプラットフォームで.現在、Linux でライブラリの初期バージョンを使用しているユーザーがいます.これは 287 のように正常に動作します. クロスプラットフォームの通常の標準 C++ コードです。

ここでも、ライブラリの作成者は 290 を最適化したいと考えています。 .今回は、組み込みの Windows サポートを使用して回答を得ることにしました:

int get_the_answer()
{
    int result;
    GetTheAnswerEx2(&result, NULL, NULL); // Windows only
    return result;
}

これも重大な変更です。WinAPI を呼び出す関数は Linux でコンパイルされません。ユーザーのコードは壊れています。そのため、ライブラリの作成者は関数を明示的に 303 としてマークする必要があります。 そうすることで、どの関数を Linux で使用できるかを文書化し、それをユーザーに約束します。

ソースコードで必須のキーワードを使用して、どのプラットフォームでどの機能が利用可能かを明示的にマークすることはありません.代わりに、明示的に文書化されていない限り、ライブラリコードはクロスプラットフォームであると想定されます.ライブラリの更新により特定のプラットフォームでコードが壊れると、ユーザーに影響します.問題を報告して重大な変更を修正してください。

私たちの場合、ライブラリの作成者は 317 を変更します .

int get_the_answer()
{
    int result;
#ifdef WIN32
    GetTheAnswerEx2(&result, NULL, NULL); // Windows only
#else
    /* expensive computation */
#endif
    return result;
}

では、「OS マーカー」がない場合、煩わしい 321 を保持する必要はありません。 マーカー?

すべてが 330 であると期待できます ライブラリが明示的に別の方法で文書化している場合を除き、前のセクションで述べた条件に従います。ライブラリの更新で壊れる何かをコンパイル時に使用すると、OS で壊れるライブラリと同じように反応します。問題とライブラリの作成者は条件付きコンパイルでそれを修正します。私たちの場合は 348 を使用します :

int get_the_answer_impl() { /* as before */ }

int get_the_answer()
{
    if (std::is_constant_evaluated()) // compile-time platform
    {
        return get_the_answer_impl();
    }
    else // other platform
    {
        // Lazily compute once.
        static int result = get_the_answer_impl();
        return result;
    }
}

関数を 354 としてマーク 文書化の目的で、関数を 366 としてマークするのと同じくらい必要です または 376 .

385 を検証する方法 関数はコンパイル時に実行できますか?

関数 396 をマークすることのもう 1 つの利点と言うかもしれません。 コンパイラが先に進み、コンパイル時に実際に動作することを確認できるということです。ただし、これは部分的にしか当てはまりません。次のコードがコンパイルされます。

constexpr int f(int i)
{
    if (i == 0)
      return 0;

    return std::getchar();
}

関数は 403 とマークされています 412 だけなのに 424 の場合 439 です;そうしないと、コンパイル時に明らかに機能しない I/O を実行します。しかし、これはまったく問題ありません。 コンパイル時に機能する引数の可能な組み合わせが 1 つある場合。これがそのケースです。

また、コンパイル時に機能する引数の組み合わせがなくても、コンパイラは診断を発行する必要さえないことに注意してください!

では、関数がコンパイル時に機能することをどのように確認するのでしょうか?

Linux で関数が動作することを確認するのと同じ方法で行います。関連するすべての引数をカバーするテストを作成します。

constexpr auto result_constexpr = foo(1, 2, 3);
CHECK(result_constexpr == 42);

auto a = 1;
auto result_runtime = foo(a, 2, 3);
CHECK(result_runtime == 42);

テストする関数が 452 を使用しない場合 実行中のプラットフォームに応じて実装を変更するには、実行時に同じコードを実行するため、実行時テストは必要ありません。465 コンパイラの実装はプロセッサと一致します。これは、あなたではなく、コンパイラの作成者が行う必要があります。

すべての結果がコンパイル時に計算され、検証のみが実行時に行われるテストを作成することには、いくつかの追加の利点があります。

  • 事前に計算された結果の同等性チェックを数回行うだけなので、テストは非常に高速に実行されます。
  • 失敗したテスト ケースをデバッグするのは簡単です:476 を削除するだけです 間違っている単一の結果からデバッガーを使用します。他のすべてはコンパイル時に計算されるため、デバッグする必要がある関数の呼び出しは 1 つだけであり、機能する他のすべての呼び出しをステップオーバーする必要はありません。
  • コンパイル時に UB はありません。コンパイラは、診断が発生するたびに診断を発行する必要があります。十分なカバレッジがあれば、関数に UB が含まれていないことを確認できます。

486 です 悪い考えですか?

494の場合 関数呼び出しがコンパイル時に発生するかどうかを照会する方法として C++20 に追加されましたが、一部の人々はそれは悪い考えだと主張しました.今では、コンパイル時にまったく異なる動作をするこのようなコードを書くことが可能です.そして実行時:

constexpr int f()
{
    if (std::is_constant_evaluated())
        return 42;
    else
        return 11;
}

明らかに、このようなコードを書くことは悪いことなので、それができないようにする必要があります.

ただし、次のように書くことは既に可能です:

constexpr int f()
{
#ifdef WIN32
        return 42;
#else
        return 11;
#endif
}

509 のこの特定の実装 悪いです。条件付きコンパイルは必須です 512 にも同じことが当てはまります。 および 521 プラットフォーム固有の API を活用するには、実行しているプラ​​ットフォームを照会し、それに応じて決定する方法が必要です。

主な例は、538 のような C++20 で追加されたビット関数です。 実行時に、コンパイル時に利用できない特別なアセンブリ命令を使用したいので、 544 を使用します 実装を切り替えます。

また、クロスプラットフォーム コードと同様に、両方のバージョンをテストして、両方が機能することを確認する必要があります。

結論

555 はプラットフォームです。

569 を書く 関数は移植可能な関数を書くのと同じです:ほとんどのコードは 574 であるべきです 、ほとんどのコードがクロスプラットフォームであるように、585 架空の 598 のように、マーカーは不要であるべきです マーカー; 600 をテストする必要があります クロスプラットフォーム コードの場合と同様に、コンパイル時および実行時に関数を実行する必要があります。また、他のすべての移植可能なコードと同様に、条件付きコンパイルを実行して最適な API を選択する方法が必要です。