私は標準でその事実の正式な説明を探しています。 3.9.1/9の内容を見つけて、そのセクションを使用して説明しようとしました。
セクション3.9.1/9、N3797:
空のタイプには空の値のセットがあります。空のタイプは、完了できない不完全なタイプです。値を返さない関数の戻り値の型として使用されます。どの式も明示的にcv void(5.4)型に変換できます。タイプvoidの式は、式ステートメント(6.2)、コンマ式(5.18)のオペランド、?:(5.16)の第2または第3オペランド、typeid、noexcept、またはdecltype。戻り値の型がvoidである関数のreturnステートメント(6.6.3)の式として、またはcv型への明示的な変換のオペランドとして。
Void型には空の値のセットがあるという事実から、それがどのように意味するのか理解できませんか?
型Tに空の値セットがあると仮定します。コンパイラが次の行に遭遇するとエラーをスローするのはなぜですか:
extern T v;
次の方法で、不完全な型の変数をデカールできます。
#include <iostream>
#include <cstring>
using namespace std;
struct Foo;
extern Foo f; //OK!
int main()
{
}
そしてそれはうまく動作します
Void型では実行できません
#include <iostream>
#include <cstring>
using namespace std;
extern void f; //compile-time error
int main()
{
}
変数はオブジェクト型または参照でなければならないため、void
型の変数を宣言できません。extern void f;
は参照を宣言せず、void
はオブジェクトタイプではありません:
セクション3 [basic]
は
変数は、非静的データメンバーまたはオブジェクトの参照以外の参照の宣言によって導入されます。
セクション3.9 [basic.types]
は
オブジェクトタイプは、(おそらくcv-qualified)タイプであり、関数タイプ、参照タイプ、
void
タイプではありません。
「void型は不完全な型です」
不完全な型の変数は作成できません
「...完了できない」
extern
の不完全な構造体の例は後の時点で完了することができますが、コンパイラはvoid
型の宣言は決して完了できないことを知っています。
[編集]以下の答えは有効な観察を行いますが、矛盾しています。これらは価値があるかもしれないので、私はそれらを削除しませんが、より簡単なアプローチのためにBen Voightの答えとコメントを見てください。
extern
宣言に関する観察は、7.1.1/8で特に許可されています。
宣言されているが未定義のクラスの名前は、extern宣言で使用できます。このような宣言は、完全なクラス型を必要としない方法でのみ使用できます。
void
は「宣言されているが未定義のクラス」ではなく、7.1.1に適用される他の例外はありません。
さらに、3.9/5は実際に許可されていることを明確に示しています。
宣言されているが定義されていない、特定のコンテキストの列挙型(7.2)、またはサイズが不明または要素型が不完全なクラスは、定義が不完全なオブジェクト型です。 [45]不完全に定義されたオブジェクトタイプとvoidタイプは不完全なタイプです(3.9.1)。オブジェクトはdefinedであってはなりません。不完全な型になります。
エンファシス鉱山。標準のこの部分は、定義と宣言の違いについて非常に具体的であるため、省略により、宣言areが許可されることを指定しています。
void
は不完全タイプです。のみそれらへのポインターを宣言し、関数シグネチャで使用できます。明らかに、extern Foo f;
isは、struct Foo
canは別のコンパイル単位で定義されます(それがnotの場合、エラーはリンカーによって検出されます)が、void
は定義できません「定義済み」(そしてもちろん、コンパイラはこれを知っています)ので、この場合、void
は非常に特別です。
変数の値のセットが空の場合、何にも使用できません。
割り当てることができる値がないため、それに割り当てることはできません。
アクセスできないため、アクセスできません。そのため、値は不定になります。
可能な値がないため、変数のサイズはありません。
void
は、可変プレースのプレースホルダーとしてのみ使用されます。関数が値を返さないことを示すために、戻り値の型として使用されます。引数リストのC
で使用され、関数が引数を取らないことを示します(言語のプロトタイプ前バージョンからのあいまいさを解決するため)。また、ポインター宣言で使用して、他のポインター型に変換できる汎用ポインターを作成します。変数宣言では、そのような類似の使用法はありません。
CとC++は、アドレスを比較することで、すべてのオブジェクトのIDを比較できると想定しているため、すべてのオブジェクトがゼロ以外の固定サイズであることを確認する必要があります。その要件に当てはまらなかった場合、実際には多くの場合、サイズがゼロのオブジェクトを宣言することがいくらか役立つことがあります[たとえば、時には有用であり、時には有用ではないフィールドを含むテンプレートを使用するコードで、またはそのような位置合わせを必要とする要素を含むことを必要とする特定の位置合わせに構造をパディングする手段として]。ただし、サイズがゼロのタイプは、すべてのオブジェクトに一意のアドレスがあることを指定するルールに、アドレスを共有できるサイズがゼロのオブジェクトの存在を許可する例外が含まれないという事実と矛盾します。
ただし、サイズがゼロのオブジェクトが許可されていても、「不明なオブジェクトへのポインター」は「サイズがゼロのオブジェクトへのポインター」と同じであってはなりません。タイプvoid*
は前者に使用されます。つまり、後者には他の何かを使用する必要があることを意味します。つまり、void
以外のものがゼロサイズのオブジェクトが指すもののタイプになります。 。
まあ-私はこの背後にある理論的根拠を本当に見ていません。この方法で、未知の型の変数を宣言できれば素晴らしいことです。 「void *」やサイズ不明の配列のようなもの。次のようなコードを想像してください:
#include <iostream>
#include <cstring>
using namespace std;
extern void f;
int main()
{
cout << (int &)f << endl; //cout 'f' as it was integer
}
struct {
int a;
double b;
} f{};
これで、実際に配列で同様のことができるようになりました。
#include <iostream>
#include <cstring>
using namespace std;
struct Foo;
extern int arr[];
int main()
{
cout << arr[2] << endl;
}
int arr[4]{};
寿命 例 。