struct X
{
X():mem(42){}
void f(int param = mem) //ERROR
{
//do something
}
private:
int mem;
};
これが C++ で違法である理由を 1 つだけ教えてもらえますか?! つまり、エラーであることはわかっています。エラーの意味はわかっていますが、なぜこれが違法なのか理解できません。
struct X
{
X():mem(42){}
void f(int param = mem) //ERROR
{
//do something
}
private:
int mem;
};
これが C++ で違法である理由を 1 つだけ教えてもらえますか?! つまり、エラーであることはわかっています。エラーの意味はわかっていますが、なぜこれが違法なのか理解できません。
あなたのコード(簡略化):
struct X
{
int mem;
void f(int param = mem); //ERROR
};
メンバー関数のパラメーターのデフォルト値として、非静的メンバーデータを使用する必要があります。頭に浮かぶ最初の質問はこれです:デフォルト値が属するクラスの特定のインスタンスはどれですか?mem
X x1 = {100}; //mem = 100
X x2 = {200}; //mem = 200
x1.f(); //param is 100 or 200? or something else?
あなたの答えは、 を持っているオブジェクトで呼び出される100
ようになるかもしれません。その場合、実装には次のように実装する必要があります。f()
x1
mem = 100
f()
void f(X* this, int param = this->mem);
次に、他の引数を初期化する前に、最初の引数を最初に初期化する必要があります。ただし、C ++標準では、関数の引数の初期化順序は指定されていません。したがって、それは許可されていません。C ++標準がこれさえ許可しないのと同じ理由で:
int f(int a, int b = a); //§8.3.6/9
実際、§8.3.6/9は明示的に次のように述べています。
デフォルトの引数は、関数が呼び出されるたびに評価されます。関数の引数の評価の順序は指定されていません。したがって、関数のパラメータは、評価されない場合でも、デフォルトの引数式で使用されないものとします。
そして、セクションの残りの部分は興味深い読み物です。
「デフォルト」引数に関連する興味深いトピック(ただし、このトピックには関連していません):
デフォルトの引数は、コンパイル時に認識されている必要があります。関数呼び出しのようなものについて話すとき、戻り値がそうでなくても、関数はコンパイル時に認識されるので、コンパイラーはそのコードを生成できますが、デフォルトでメンバー変数を使用すると、コンパイラーは生成しません。コンパイル時にそのインスタンスを見つける場所がわからないthis
ため、memを見つけるにはパラメータ()を効果的に渡す必要があります。次のようなことはできずvoid func(int i, int f = g(i));
、2つは事実上同じ制限であることに注意してください。
また、この制限はばかげていると思います。しかし、C++にはばかげた制限がたくさんあります。
DeadMGが上で述べたように、
void func(int i, int f = g(i))
同じ理由で違法です。しかし、それは単なるばかげた制限ではないと思います。このような構成を可能にするには、関数パラメーターの評価順序を制限する必要があります (this->mem の前に this を計算する必要があるため) が、c++ 標準では、評価順序に関する仮定を明示的に拒否しています。
重複した質問で受け入れられた答えは理由ですが、標準では、これがそうである理由も明示的に述べています。
8.3.6/9:
" 例: 次の例の X::mem1() の宣言は、初期化子として使用される非静的メンバー X::a にオブジェクトが提供されていないため、形式が正しくありません。
int b;
class X
int a;
int mem1(int i = a); // error: nonstatic member a
// used as default argument
int mem2(int i = b); // OK: use X::b
static int b;
};
ただし、静的メンバー X::b にアクセスするためにオブジェクトが必要ないため、X::mem2() の宣言は意味があります。クラス、オブジェクト、およびメンバーは、9 節で説明されています。"
...そして、その時点での値を解決するために必要なオブジェクトを提供する構文が存在しないため、X::a
非静的メンバー変数をデフォルト引数の初期化子として使用することは事実上不可能です。
理由の 1 つf
は、公開されているがmem
非公開であるためです。そのため、次のようなコード:
int main() {
X x;
x.f();
return 0;
}
... X のプライベート データを取得する外部コードが含まれます。
それとは別に、それはコード生成を少しトリッキーにするでしょう (または少なくとも可能性があります)。通常、コンパイラがデフォルトの引数を使用する場合、関数宣言の一部として渡す値を取得します。その値をパラメーターとして渡すコードを生成するのは簡単です。オブジェクトのメンバーを渡している可能性があり (任意に深くネストされている可能性があります)、テンプレートに依存する名前である可能性などを追加すると、(たとえば) 正しいターゲットへの変換で別のオブジェクトに名前を付ける可能性がありますタイプし、コード生成をかなり難しくするためのレシピがあります。確かなことはわかりませんが、誰かがそのようなことを考えて、保守的でいるほうがよいと判断したのではないかと思います。正当な理由が見つかった場合は、後で薄くなります。問題が発生するのを何度も見てきたことを考えると、問題が発生することはめったにないという理由だけで、そのままの状態が長く続くと思います。
コンパイラは、コンパイル時にデフォルト値を維持するためにアドレスを認識している必要があります。非静的メンバー変数のアドレスは、コンパイル時には不明です。
ISO C++ セクション 8.3.6/9
非静的メンバーは、クラス メンバー アクセス式 (5.2.5) の id-expression として表示されない限り、またはメンバーへのポインターを形成するために使用されない限り、たとえ評価されなくても、既定の引数式で使用されません。 (5.3.1)。
また、そのセクションに示されている例も確認してください。
他のすべての回答は問題について話し合っているだけなので、解決策を投稿すると思いました。
デフォルト引数のない他の言語で使用されているように (例: C# 4.0 より前)
オーバーロードを使用するだけで同じ結果が得られます。
struct X
{
X():mem(42){}
void f(int param)
{
//do something
}
void f()
{
f(mem);
}
private:
int mem;
};
デフォルトの引数は、異なるコンテキストで 2 つの異なる手順で評価されます。
最初に、デフォルト引数の名前検索が宣言のコンテキストで実行されます。
次に、デフォルト引数の評価は、実際の関数呼び出しのコンテキストで実行されます。
実装が過度に複雑にならないようにするために、デフォルト引数として使用できる式にいくつかの制限が適用されます。
this->
通常は呼び出しサイトで評価できない (暗黙の) 修飾が必要なため、使用できません。