定数ポインタを定数式にできないのはなぜですか?
-
29-10-2019 - |
質問
次のプログラムがコンパイルされます:
template <const int * P>
class Test{};
extern const int var = 42; //extern needed to force external linkage
int main()
{
Test<&var> test;
}
これは、しかし、私のために驚きである、そうではありません:
template <const int * P>
class Test{};
extern const int var = 42; //extern needed to force external linkage
extern const int * const ptr = &var; //extern needed to force external linkage
int main()
{
Test<ptr> test; //FAIL! Expected constant expression.
}
代替例:
int main()
{
const int size = 42;
int ok[*&size]; //OK
const int * const pSize = &size;
int fail[*pSize]; //FAIL
}
私は、ポインタがconstであり、定数式で初期化されているかどうかにかかわらず、ポインタは定数式になることはできないと結論付けました。
質問:
- 私の結論は本当ですか?
- もしそうなら、ポインタを定数式にできないのはなぜですか?そうでない場合は、上記のプログラムがコンパイルされないのはなぜですか?
- C++0x(C++11)は何かを変更しますか?
任意の洞察をありがとう!
解決
もう少し複雑です。 C ++ 03およびC ++ 11では、&var
がローカル静的/クラス静的または名前空間スコープ変数である場合、var
は定数式です。これをアドレス定数式と呼びます。その定数式を使用したクラス静的または名前空間スコープポインタ変数の初期化は、定数式であるため、コードが実行される前に実行されることが保証されます(静的初期化フェーズ)。
ただし、C ++ 11以降のみ、アドレス&var
を格納する constexpr ポインター変数もアドレス定数式として使用でき、C ++ 11以降のみ、アドレス定数を逆参照できます。式(実際には、さらに多くの逆参照が可能です-ローカル配列要素のアドレスでも、トピックを維持しましょう)、逆参照の前に初期化された定数整数変数またはconstexpr変数を参照する場合は、再び定数式を取得します(タイプと値のカテゴリ、定数式の種類は異なる場合があります)。そのため、以下は有効なC ++ 11です。
ジェネラコディセタグプレ
もしそうなら、なぜポインタを定数式にできないのですか?そうでない場合は、なぜ上記のプログラムがコンパイルされないのですか?
これは、標準の文言における既知の制限です。現在、ポインタ型のテンプレートパラメータに対して、引数または
& object
として他のテンプレートパラメータのみを許可しています。コンパイラはもっと多くのことができるはずですが。
他のヒント
C++0xではまだ許可されていません。 temp.arg.nontype
が必要です。:
非型、非テンプレートtemplate-parameterのtemplate-argumentは、次のいずれかになります:
- 整数型または列挙型の非型template-parameterの場合、template-parameterの型の変換された定数式(5.19);または
- 非型テンプレートパラメータの名前;または
- 静的記憶域期間を持つオブジェクトのアドレスを指定する定数式(5.19)と、静的記憶域期間を持つオブジェクトのアドレスを指定する定数式(5.19) 外部リンケージまたは内部リンケージ、または機能テンプレートを含む外部リンケージまたは内部リンケージを持つ機能 および関数テンプレートidですが、非静的クラスメンバーを除く, (括弧を無視して)次のように表現されます
&
id-expression
, ただし、名前が関数または配列を参照している場合は&を省略することができます。 対応するtemplate-parameterが参照の場合は省略されます;または- ヌルポインタ値に評価される定数式(4.10);または
- nullメンバポインタ値に評価される定数式(4.11);または
- 5.3.1で説明されているように表現されたメンバーへのポインタ。
元の答え:
- C++03では、整数式のみを定数式にすることができます。
- 標準がそう言っているからです(当然)。
- C++0xでは、n3290には以下を使用する例が含まれています
constexpr
ポインタに。だからあなたがしようとしていることは今可能でなければなりませんが、あなたは今すぐ使用しなければなりませんconstexpr
トップレベルの代わりにキーワードconst
.
Gccのバグも含まれています, g++は、標準ドラフト独自の有効な例を拒否します constexpr
使用法.
問題は、C ++プログラムをメモリ内の任意の場所にロードできるため、プログラムを実行するたびにグローバルジェネラコダイスタグコードのアドレスが異なる可能性があるためです。プログラムを2回実行するとどうなりますか?その場合、var
は明らかに2つの異なる場所にあります。
さらに悪いことに、あなたの例では、スタック上の変数のアドレスを取得します!これを見てください: ジェネラコディセタグプレ
mainがmyfunction(3)を呼び出すと、3つのmyvarが別々の場所に作成されます。コンパイル時に多くの myvarがどのように作成されているかを知る方法はありません。ましてや、正確な場所はありません。
最後に:変数をvar
として宣言することは、「約束する」ことを意味し、コンパイル時定数であることを意味するわけではありません。 この例を参照してください:
ジェネラコディセタグプレ