web-dev-qa-db-ja.com

いつ、なぜconstexprでstaticを使用するのですか?

免責事項として、私は尋ねる前にこれに関する私の研究をしました。私は 同様のSO質問 を見つけましたが、その答えは少し「わらびき」と感じ、個人的には質問に実際には答えませんでした。また、私の便利な cppreference page ですが、それはほとんどの場合、非常に「だまされた」説明を提供しません。

基本的に私はまだconstexprを増やしていますが、現時点で理解しているのは、コンパイル時に式を評価する必要があるということです。これらはコンパイル時にのみ存在する可能性があるため、実行時に実際にメモリアドレスを持つことはありません。したがって、(たとえば、クラスのように)static constexprを使用している人を見ると、混乱します... staticは、ランタイムコンテキストでのみ有用であるため、ここでは不要です。

constexprはコンパイル時の式以外は何も許可しない」ステートメント(特にここSOで)に矛盾を見てきました。ただし、 Bjarne Stroustrupのページからの記事 は、実際にconstexprdoesが評価を必要とすることをさまざまな例で説明していますコンパイル時の式の。そうでない場合は、コンパイラエラーが生成されます。

前の段落は少しトピックから外れているように見えますが、staticconstexprと共に使用できる、または使用する必要がある理由を理解するために必要なベースラインです。残念ながら、そのベースラインには多くの矛盾する情報が浮かんでいます。

誰かが私がこの情報のすべてを一緒に、意味のある例と概念を使って純粋な事実にまとめるのを手伝ってくれる?基本的に、constexprが実際にどのように動作するかを理解するとともに、なぜstaticを使用するのですか?そして、それらを一緒に使用できる場合、static constexprはどのスコープ/シナリオを介して意味がありますか?

25
void.pointer

constexpr変数はコンパイル時の値ではありません

値は不変であり、ストレージを占有しません(アドレスはありません)。ただし、constexprとして宣言されたオブジェクトは変更可能であり、(as-ifルールの下で)ストレージを占有します。

変異性

constexprとして宣言されたほとんどのオブジェクトは不変ですが、次のように(部分的に)変更可能なconstexprオブジェクトを定義できます。

struct S {
    mutable int m;
};

int main() {
    constexpr S s{42};
    int arr[s.m];       // error: s.m is not a constant expression
    s.m = 21;           // ok, assigning to a mutable member of a const object
}

ストレージ

コンパイラは、as-ifルールの下で、notを選択して、constexprとして宣言されたオブジェクトの値を格納するためのストレージを割り当てることができます。同様に、非constexpr変数に対してそのような最適化を行うことができます。ただし、インライン化されていない関数にオブジェクトのアドレスを渡す必要がある場合を考えてください。例えば:

struct data {
    int i;
    double d;
    // some more members
};
int my_algorithm(data const*, int);

int main() {
    constexpr data precomputed = /*...*/;
    int const i = /*run-time value*/;
    my_algorithm(&precomputed, i);
}

コンパイラーは、アドレスをいくつかの非インライン関数に渡すために、precomputedにストレージを割り当てる必要があります。コンパイラがprecomputedおよびiのストレージを連続して割り当てることができます。これがパフォーマンスに影響を与える可能性がある状況を想像できます(以下を参照)。

標準

変数はオブジェクトまたは参照です 【基本】/ 6。オブジェクトに焦点を当てましょう。

constexpr int a = 42;のような宣言は、文法的にはsimple-declaration;です。 decl-specifier-seqで構成されていますinit-declarator-list;

[dcl.dcl]/9から、そのような宣言がオブジェクトを宣言していると結論付けることができます(厳密にはではありません)。具体的には、(厳密に)オブジェクト宣言であると結論付けることができますが、これには参照の宣言が含まれます。 void 型の変数を使用できるかどうかの説明も参照してください。

オブジェクトの宣言のconstexprは、オブジェクトのタイプがconstであることを意味します [dcl.constexpr]/9。オブジェクトはストレージの領域です[intro.object]/1。 [intro.object]/6と[intro.memory] ​​/ 1から、すべてのオブジェクトにアドレスがあると推測できます。このアドレスを直接取得できない場合があることに注意してください。オブジェクトがprvalueを介して参照される場合。 (リテラル42など、オブジェクトではないprvalueもあります。)2つの異なる完全なオブジェクトは、異なるアドレスを持つ必要があります。[intro.object]/6

この時点から、constexprとして宣言されたオブジェクトは、他の(完全な)オブジェクトに対して一意のアドレスを持つ必要があると結論付けることができます。

さらに、宣言constexpr int a = 42;は、一意のアドレスを持つオブジェクトを宣言していると結論付けることができます。

静的およびconstexpr

私見の唯一の興味深い問題は、「関数ごとのstatic」です。アラ

void foo() {
    static constexpr int i = 42;
}

私の知る限り-しかし これはまだ完全に明確ではないようです -コンパイラmay実行時にconstexpr変数の初期化子を計算します-時間。しかし、これは病的なようです。 notであると仮定しましょう。つまり、コンパイル時に初期化子を事前計算します。

static constexprローカル変数の初期化は静的初期化の間に行われ、これは動的初期化の前に実行する必要があります[basic.start.init]/2。これは保証されていませんが、これにより実行時/ロード時のコストが発生しないと推測できます。また、定数の初期化には同時実行の問題がないので、static変数がすでに初期化されているかどうかをthread-safeランタイムチェックする必要がないと考えることができます。 (clangとgccのソースを調べると、これらの問題に光が当てられるはずです。)

非静的ローカル変数の初期化では、定数初期化中にコンパイラーが変数を初期化できない場合があります。

void non_inlined_function(int const*);

void recurse(int const i) {
    constexpr int c = 42;
    // a different address is guaranteed for `c` for each recursion step
    non_inlined_function(&c);
    if(i > 0) recurse(i-1);
}

int main() {
    int i;
    std::cin >> i;
    recurse(i);
}

結論

どうやら、一部のケースでは、static constexpr変数の静的ストレージ期間の恩恵を受けることができます。ただし、この回答の「ストレージ」セクションに示されているように、このローカル変数の局所性が失われる可能性があります。これが実際の効果であることを示すベンチマークが表示されるまで、これは関係がないと思います。

staticオブジェクトに対するconstexprのこれらの2つの影響のみがある場合、デフォルトでstaticを使用します。通常、constexprオブジェクトの一意のアドレスを保証する必要はありません。

可変のconstexprオブジェクト(mutableメンバーを持つクラス型)の場合、ローカルのstaticオブジェクトと非静的なconstexprオブジェクトの間には明らかに異なるセマンティクスがあります。同様に、アドレス自体の値が関連している場合(たとえば、ハッシュマップ検索の場合)。

17
dyp

例のみ。コミュニティウィキ。

static ==関数ごと(静的ストレージ期間)

constexprとして宣言されたオブジェクトは、他のオブジェクトと同じようにアドレスを持っています。何らかの理由でオブジェクトのアドレスが使用されている場合、コンパイラーはそのオブジェクトにストレージを割り当てる必要がある場合があります。

constexpr int expensive_computation(int n); // defined elsewhere

void foo(int const p = 3) {
    constexpr static int bar = expensive_computation(42);
    std::cout << static_cast<void const*>(&bar) << "\n";
    if(p) foo(p-1);
}

変数のアドレスは、すべての呼び出しで同じになります。すべての関数呼び出しでスタックスペースは必要ありません。と比較:

void foo(int const p = 3) {
    constexpr int bar = expensive_computation(42);
    std::cout << static_cast<void const*>(&bar) << "\n";
    if(p) foo(p-1);
}

ここで、アドレスはfooの(再帰的な)呼び出しごとにdifferentになります。

これは、たとえばオブジェクトが大きい場合(配列など)、定数式が必要なコンテキスト(コンパイル時定数が必要)で両方を使用する必要があり、そのアドレスを取得する必要がある場合に重要です。

アドレスは異なる必要があるため、オブジェクトは初期化される可能性があります実行時に;たとえば、再帰の深さがランタイムパラメータに依存する場合などです。イニシャライザはまだ事前に計算されている可能性がありますが、結果は再帰ステップごとに新しいメモリ領域にコピーする必要がある場合があります。その場合、constexprは、初期化子canがコンパイル時に評価され、初期化couldが変数のコンパイル時に実行されることのみを保証しますそのタイプの。

static ==クラスごと

template<int N>
struct foo
{
    static constexpr int n = N;
};

いつもと同じ:fooの各テンプレート特殊化(インスタンス化)の変数を宣言します。 foo<1>foo<42>foo<1729>。タイプではないテンプレートパラメータを公開する場合は、たとえば、静的データメンバー。 constexprにすることができるため、他の人はコンパイル時に既知の値から利益を得ることができます。

static ==内部リンケージ

// namespace-scope
static constexpr int x = 42;

かなり冗長です。 constexpr変数には、デフォルトで内部リンケージがあります。この場合、現在staticを使用する理由はありません。

7
dyp

正確な型定義がわからないが、型に関するいくつかの情報を照会したい場所で、名前のない列挙型の代わりにstatic constexprを使用します(通常、コンパイル時)

コンパイル時の名前のない列挙型には、いくつかの利点があります。より簡単なデバッグ(値は「通常の」変数のようにデバッガーに表示されます。また、列挙型の数値だけではなく、constexprで構成できる任意の型(数値だけでなく)を使用できます。

例:

template<size_t item_count, size_t item_size> struct item_information
{
    static constexpr size_t count_ = item_count;
    static constexpr size_t size_ = item_size;
};

これで、コンパイル時にこれらの変数にアクセスできます。

using t = item_information <5, 10>;
constexpr size_t total = t::count_ * t::size_;

代替案:

template<size_t item_count, size_t item_size> struct item_information
{
    enum { count_ = item_count };
    enum { size_ = item_size };
};

template<size_t item_count, size_t item_size> struct item_information
{
    static const size_t count_ = item_count;
    static const size_t size_ = item_size;
};

代替手段には、静的constexprの利点のすべてが含まれているわけではありません-コンパイル時の処理、タイプセーフ、および(潜在的に)メモリ使用量の低下が保証されています(constexpr変数はメモリを占有する必要がないため、効果的に困難です)可能な場合を除いてコード化されます)。

Constexpr変数のアドレスの取得を開始しない限り(そして、まだ実行している場合でも)、標準の静的constで見られるようなクラスのサイズの増加はありません。

1
Michael Gazonda