web-dev-qa-db-ja.com

オーバーロードされた演算子をクラスの静的メンバーとして定義できないのはなぜですか?

C++構文では、次のような構造体/クラス内でオーバーロードされた演算子を定義できます。

struct X
{
   void operator+(X);
}

または次のような構造体/クラスの外部:

void operator+(X, X);

ではなく

struct X
{
   static void operator+(X, X);
}

誰もがこの決定の理由を知っていますか? 3番目のフォームが許可されないのはなぜですか? (MSVCは構文エラーを返します)。たぶん、この背後に何か物語がありますか?

追伸最初と2番目の定義が同時に存在すると、あいまいさが生じます。

1>CppTest1.cxx
1>c:\ballerup\misc\cf_html\cpptest1.cxx(39) : error C2593: 'operator +' is ambiguous
1>        c:\ballerup\misc\cf_html\cpptest1.cxx(13): could be 'void B1::operator +(B1 &)'
1>        c:\ballerup\misc\cf_html\cpptest1.cxx(16): or       'void operator +(B1 &,B1 &)'
1>        while trying to match the argument list '(B1, B1)'

このあいまいさが、1、3または2、3の間に勝る理由がわかりません。

40
Kirill Kobelev

私はこの概念に関するC++の議論に関する具体的な知識は持っていないので、これを無視してください。

しかし、私には、逆の質問があります。質問は、「この構文が許可

現在の構文よりも利点はまったくありません。非静的メンバー関数バージョンは、提案された静的バージョンと同じプライベートメンバーへのアクセス権を持ちます。したがって、プライベートにアクセスして実装する必要がある場合は、クラスのほとんどのメンバーで通常行うように、静的でないメンバーにするだけです。

非対称演算子(つまり:operator+(const X &x, const Y &y))の実装が簡単になるわけではありません。これを実装するためにプライベートアクセスが必要な場合、クラスの1つでそれらのフレンド宣言が必要になります。

だから私はそれが存在しない理由はそれがnecessaryではないということだと言うでしょう。非メンバー関数と非静的メンバーの間で、必要なすべてのユースケースがカバーされます。


または、別の言い方をすれば:

自由関数は、静的関数システムができるすべてのことを行うことができます以上

無料の関数を使用することで、テンプレートで使用される演算子の引数依存のルックアップを取得できます。静的関数は特定のクラスのメンバーである必要があるため、静的関数ではできません。また、名前空間に追加することはできますが、クラスの外部からクラスにaddを追加することはできません。したがって、ADLコードを機能させるために特定のネームスペースに演算子を配置する必要がある場合は、可能です。静的関数演算子ではできません。

したがって、自由関数は、提案された静的関数システムが提供するすべてのもののスーパーセットです。許可してもメリットがないため、許可するreasonはありません。したがって、許可されません。


ファンクタをインスタンス化せずに使用できるようにしますか?

それは用語の矛盾です。 「ファンクター」は「関数オブジェクト」です。タイプはオブジェクトではない;です。したがって、ファンクターになることはできません。インスタンス化されると、ファンクターになるタイプにすることができます。しかし、タイプだけではファンクターにはなりません。

さらに、Typename::operator() staticを宣言できるということは、Typename()があなたが望むことをするということではありません。この構文にはすでに実際の意味があります。デフォルトのコンストラクターを呼び出して、Typenameテンポラリーをインスタンス化します。

最後に、すべてのwere n'tの場合でも、実際には何が良いでしょうか?何らかのタイプの呼び出し可能オブジェクトを受け取るほとんどのテンプレート関数は、ファンクターと同様に関数ポインターでも同様に機能します。単にファンクターだけでなく、内部データを持つcannotのファンクターにインターフェースを制限したいのはなぜですか?つまり、キャプチャラムダなどを渡すことはできません。

状態を含めることができないファンクターは何が良いでしょうか?なぜ状態を持たない「ファンクター」をユーザーに渡すように強制したいのですか?そして、なぜユーザーがラムダを使用できないようにしたいのですか?

したがって、あなたの質問は間違った仮定から派生しています。たとえそれがあったとしても、あなたが望むものをあなたに与えないでしょう。

18
Nicol Bolas

callのような演算子には明らかな構文がないため、何かを構成する必要があります。以下の変数を考慮してください。

X x1;
X x2;

さて、演算子の代わりに通常のメンバー関数を使用しているふりをしましょう-あなたの例でoperator+plusに変更したとしましょう。

3つの呼び出しはそれぞれ次のようになります。

x1.plus(x2);
plus(x1, x2);
X::plus(x1, x2);

+を使用して演算子呼び出しを行う場合、コンパイラはXのスコープで演算子を検索することをどのように知るでしょうか?通常の静的メンバー関数に対してはできません。また、演算子には、明確にするための特別な分配が与えられていません。

ここで、プログラムでbothの2番目と3番目のフォームが宣言されているかどうかを検討してください。 x1 + x2と言った場合、コンパイラは常にフリー関数を選択する必要があるか、呼び出しがあいまいになります。唯一の本当の代替案は、x1 X::+ x2のようなもので、見た目がjustいだけです。それを考えると、標準委員会は静的メンバーバージョンを単に禁止することを決定したと確信しています。静的メンバーバージョンでは、フレンドフリー機能を使用すれば何でもできるからです。

14
Mark B

静的メンバー関数は、クラスを支援するユーティリティに使用できますが、何らかの理由でメンバーではありません。静的クラスメンバー関数として表されるユーティリティの中で、演算子を使用すると便利な場合があることは容易に想像できます。

もちろん、オーバーロードされた演算子がクラスCを主引数としてとる場合、それをクラスCの静的メンバーにしたいという正当な理由はありません。単に非静的メンバーにできるため、暗黙的にその引数を取得します。

ただし、クラスCの静的メンバーは、C以外のクラスでオーバーロードされた演算子である場合があります。

ファイルスコープoperator ==(const widget &, const widget &);が存在するとします。 squiggleクラスでは、widgetオブジェクトを操作していますが、それらに対して別の比較が必要です。

自分でstatic squiggle::operator == (const widget &, const widget &);を作成できるはずです。

クラススコープから、これは簡単に呼び出すことができます。

void squiggle::memb(widget a, widget b)
{
   if (a == b) { ... } // calls static == operator for widgets
}

クラススコープの外側から、明示的な演算子呼び出し構文と組み合わせた明示的なスコープ解決を使用してのみ呼び出すことができます:

void nonmemb(widget a, widget b)
{
   a == b;  // calls the widget member function or perhaps nonstatic operator
   squiggle::operator ==(a, b); // calls squiggle class' utility
}

これは悪い考えではありません。さらに、我々はcan演算子ではなく、通常のオーバーロード関数でそれを行います。ウィジェットの比較がcompare関数を使用して行われる場合、非メンバーcompareまたはwidget::compareそしてsquiggle::comparewidgetsを取ります。

したがって、C++でサポートされていないこの唯一の側面は、演算子を使用した構文糖衣です。

おそらく、サポートを保証するのに十分なアイデアではありません(これまでのところ!)結局のところ、これはC++プログラムの革命的な再編成を可能にするものではありません。しかし、それは言語の不完全さを修正するでしょう。

また、演算子newおよびdeleteのクラスオーバーロードは暗黙的に静的であると考えてください!そのため、不完全性にはすでに少しの例外があります。

2
Kaz

うーん...暗黙的にすべてのコンストラクターを削除する静的なoperator()を考えています...これは、型付き関数のようなものになります。時々、C++でそれがあればいいのにと思います。

1
Slava

静的演算子+を許可すると直接的な欠点が生じることはありません(十分に長く考えれば、何らかの理論が生まれるでしょう)。しかし、少なくとも、Bjarne Stroustrupによって宣言された「使用しないものにお金を払わない」という原則は、十分に良い答えだと思います。より複雑な構文を除いて、静的演算子が許可された場合、何が得られますか(「+」ではなく「X :: operator +」をどこにでも記述する必要があります)?

0
mvidelgauz

基本的に、クラスメンバーの静的演算子は、非静的メンバーよりも何も買いません。

クラスに対して定義された演算子は、そのクラス型の少なくとも1つの引数をとる必要があります。

メンバー演算子は、暗黙的なthisパラメーターの形式でその引数を取ります。

非メンバー演算子には、そのクラス型の明示的な引数があります。

演算子関数への演算子インターフェイスは関係ありません。 a + bを呼び出すと、aパラメーターを介して、または明示的に宣言されたパラメーターとしてthisを渡すコードを生成します。そのため、演算子の使用方法について、静的と非静的のニュアンスの違いを表現していません。

突然、最新のISO C++が静的メンバー演算子をサポートする必要があるという要件が導入されたとします。 急いで、この要件は、次のパターンに従ってソースからソースへの書き換えによって実装できます:

static whatever class::operator *(class &x) { x.fun(); return foo(x); }

-->

whatever class::operator *() { (*this).fun(); return foo(*this); }

-->

whatever class::operator *() { fun(); return foo(*this); }

コンパイラーはstaticメンバー演算子を非静的に書き換え、左端のパラメーターを削除し、(適切な字句衛生wrtシャドウイングを使用して)そのパラメーターへのすべての参照を式*this(不要な使用省略できます)。

この変換は非常に単純であるため、プログラマーは最初からコードをそのように書くことができます。

static演算子関数定義メカニズムはそれほど強力ではありません。たとえば、virtualにはできませんが、非静的なものにはできます。

0
Kaz

これが理由かもしれません。

operatorには1つ以上のoperandsが必要だからです。したがって、staticとして宣言する場合、オブジェクトを使用して呼び出すことはできません(オペランド)。

オブジェクト以外の何ものでもないオペランドで呼び出すためには、関数は非静的でなければなりません。

以下は、関数のオーバーロードを実行している間に満たされなければならない条件です。

  • ユーザー定義型のオペランドが少なくとも1つ必要です。

したがって、演算子のオーバーロード関数を静的として宣言するとします。この場合、上記のすべての条件の1番目は満たされません。

もう1つの理由は、静的関数内では静的データメンバーのみにアクセスできることです。ただし、演​​算子のオーバーロードを実行している間は、すべてのデータメンバーにアクセスする必要があります。したがって、演算子のオーバーロード関数を静的として宣言する場合、すべてのデータメンバーにアクセスすることはできません。

したがって、演算子のオーバーロード関数はnon-static member function

しかし、例外があります。

演算子のオーバーロードにフレンド関数を使用する場合、静的として宣言できます。

0
Narendra