web-dev-qa-db-ja.com

クラス内の実際の静的定数のenum vs constexpr

私の意図を述べることから始めましょう。昔(C++)の日には、次のようなコードがありました。

class C
{
public:
  enum {SOME_VALUE=27};
};

次に、コード全体でSOME_VALUEをコンパイル時定数として使用し、コンパイラがC::SOME_VALUEを参照する場所であればどこでも、リテラル27を挿入します。

今では、そのコードを次のようなものに変更する方がより受け入れやすいようです。

class C
{
public:
  static constexpr int SOME_VALUE=27;
};

これは非常にきれいに見え、SOME_VALUEに明確に定義された型を与え、C++ 11の時点で推奨されるアプローチのようです。 (少なくとも私にとっては予期しない)問題は、これによりSOME_VALUEを外部にする必要があるシナリオも発生することです。つまり、どこかのcppファイルに次を追加する必要があります。

constexpr int C::SOME_VALUE; // Now C::SOME_VALUE has external linkage

これを引き起こすケースは、SOME_VALUEへのconst参照が使用されている場合のようです。これは、C++標準ライブラリコードで頻繁に発生します(この質問の最後の例を参照)。ところで、コンパイラーとしてgcc 4.7.2を使用しています。

このジレンマのため、静的なconstexprのすべてではなく一部の定義をcppファイルに追加する必要を避けるために、SOME_VALUEを列挙(つまり、古い学校)として定義することに戻さざるを得ません。メンバー変数。 constexpr int SOME_VALUE=27SOME_VALUEがコンパイル時定数としてonlyとして扱われることを意味することをコンパイラに伝える方法はありませんか外部リンケージを持つオブジェクトはありませんか? const参照が使用されている場合は、一時を作成します。アドレスが取得されている場合、それが必要な場合はコンパイル時エラーを生成します。これはコンパイル時定数であり、それ以上ではないためです。

以下は、cppファイルにSOME_VALUEの定義を追加する必要がある、一見良性と思われるサンプルコードです(もう一度、gcc 4.7.2でテストされています)。

#include <vector>

class C
{
public:
  static constexpr int SOME_VALUE=5;
};

int main()
{
  std::vector<int> iv;

  iv.Push_back(C::SOME_VALUE); // Will cause an undefined reference error
                               // at link time, because the compiler isn't smart
                               // enough to treat C::SOME_VALUE as the literal 5
                               // even though it's obvious at compile time
}

ファイルスコープのコードに次の行を追加すると、エラーが解決します。

constexpr int C::SOME_VALUE;
65

レコードについては、static constexprバージョンはC++ 17で期待したとおりに機能します。 N4618 Annex D.1から [depr.static_constexpr]

D.1 static constexprデータメンバーの再宣言[depr.static_constexpr]

以前のC++国際標準との互換性のために、constexpr静的データメンバーは、初期化子なしでクラスの外部で冗長に再宣言される場合があります。この使用法は非推奨です。 [例:

struct A {
 static constexpr int n = 5; // definition (declaration in C++ 2014)
};

constexpr int A::n; // redundant declaration (definition in C++ 2014)

end example]

これを可能にする関連する標準テキストはN4618 9.2.3 [class.static.data]/

[...]インライン静的データメンバーは、クラス定義で定義でき、brace-or-equal-initializerを指定できます。メンバーがconstexpr指定子で宣言されている場合、初期化子なしで名前空間スコープで再宣言できます(この使用法は非推奨です。D.1を参照)。 [...]

これには、同じものの非constexprバージョンを導入したのと同じ機構が付属しています インライン静的データメンバー

struct A {
 static inline int n = 5; // definition (illegal in C++ 2014)
}; 

inline int A::n; // illegal
8
TBBle

ここには3つのオプションがあります。

  1. クラスがテンプレートの場合、静的メンバーの定義をヘッダー自体に配置します。コンパイラは、複数の翻訳単位でのみ1つの定義として識別するために必要です([basic.def.odr]/5を参照)

  2. クラスが非テンプレートの場合、ソースファイルに簡単に配置できます。

  3. または、constexpr静的メンバー関数getSomeValue()を宣言します。

    class C
    {
    public:
        static constexpr int getSomeValue() { return 27; }
    };
    
9
apoorvkul

私は列挙型クラスに行きます:

http://en.cppreference.com/w/cpp/language/enum

http://www.stroustrup.com/C++11FAQ.html#enum

最初のリンクから:

enum class Color { RED, GREEN=20, BLUE};
Color r = Color::BLUE;
switch(r) {
    case Color::RED : std::cout << "red\n"; break;
    case Color::GREEN : std::cout << "green\n"; break;
    case Color::BLUE : std::cout << "blue\n"; break;
}
// int n = r; // error: no scoped enum to int conversion
int n = static_cast<int>(r); // OK, n = 21

C++標準N3797 S3.5/2-3から

名前は、別のスコープの宣言によって導入された名前と同じオブジェクト、参照、関数、型、テンプレート、名前空間、または値を示す可能性がある場合、リンケージがあると言われます。

—名前に外部リンケージがある場合、それが示すエンティティは、他の翻訳単位のスコープまたは同じ翻訳単位の他のスコープからの名前で参照できます。

—名前に内部リンケージがある場合、それが示すエンティティは、同じ変換単位内の他のスコープからの名前で参照できます。

—名前にリンケージがない場合、それが示すエンティティは他のスコープからの名前で参照できません。

名前空間スコープ(3.3.6)を持つ名前は、名前が

—明示的に静的と宣言された変数、関数、または関数テンプレート。または、

—明示的にconstまたはconstexprとして宣言され、明示的にexternとして宣言されておらず、以前に外部リンケージがあると宣言されていない不揮発性変数。または

—匿名ユニオンのデータメンバー。

私の読書は、次のコードでそれです:

public:
  static constexpr int SOME_VALUE=5;
  constexpr int SOME_VALUE=5;
};
static constexpr int SOME_VALUE=5;
constexpr int SOME_VALUE=5;

SOME_VALUEの4つのインスタンスすべてに内部リンケージがあります。同じ翻訳単位のSOME_VALUEへの参照でリンクし、他の場所では表示されないようにする必要があります。

明らかに最初のものは宣言であり、定義ではありません。同じ翻訳単位内の定義が必要です。 GCCがそう言ってMSVCがそうしない場合、MSVCは間違っています。

列挙型を置き換えるために、2番で問題なく動作するはずです。 staticキーワードなしの内部リンケージがまだあります。

[コメントに応じて編集]

1
david.pfx

現在、推奨される方法は次のとおりです。

enum class : int C { SOME_VALUE = 5 };
1
edwinc