質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.63%

c++ のクラスで否定演算子!を積極的にオーバーロードする場合はどんな時なのでしょうか?

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 3
  • VIEW 1,131

myoon

score 86

とりあえず、c++11 をある程度(言語が巨大なのでほんの少し)勉強してから、その上を勉強しようかなと思っているレベルの者です。

知りたいこと

c++ のクラスに、論理否定演算子「!」をオーバーロードする場合はどんな場合なのでしょうか?
例えば std::basic_ios では operator!() がオーバーロードされていますが、同時に、キャスト演算子 explicit operator bool() const;もオーバーロードされています。

しかし、operator bool() があれば、たとえexplicitがついていても、否定演算子!のオペランドの場合は明示的なキャストなしにbool型に変換されます。
またbool() と operator!() の両方がクラスAでオーバーロードされている場合、a が A のインスタンスのとき、!a と !static_cast<bool>(a) は同じ動作をするようにしないと使う方が混乱します。
とすると、多くの場合、 explicit operator bool() のみがあれば大丈夫のような気がするのですが、あえて 否定演算子のオーバーロードをする積極的な理由は何なのでしょうか?
たとえば、std::shared_ptr では!のオーバーロードはないようですね。
もし、論理演算子をオーバーロードする必用が在る場合でも「&&」「||」は短絡評価ができなくなるため危険ですから、普通は「!」しかできませんが。

自分でクラスを作るとき、!のオーバーロードを行うか否か?の指針が知りたいです。

試したこと

// test-not2.cpp
#include <iostream>

class A {
public:
  operator int() const {return 0;} // explicit をつけると当然のようにエラー
};

class B {
public:
  explicit operator bool() const {return false;}
};

class C {
public:
  bool operator!() const { return false;}
};

class D {
public:
  explicit operator bool() const {
    std::cerr << "D::operator bool()" << std::endl;
    return false;
  }
  bool operator!() const {
    std::cerr << "D::operator !()" << std::endl;
    return false;
  }
};


using std::cout;
using std::endl;

int main(){
  A a;
  B b;
  C c;
  D d;

  cout << "!a: " << !a << endl;
  cout << "!b: " << !b << endl;
  cout << "!c: " << !c << endl;
  cout << "!d: " << !d << endl;
}

出力

bash@test-not2$ ./a.out
!a: 1
!b: 1
!c: 0
!d: D::operator !()
0
bash@test-not2$ 


g++ (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0
では、両方オーバーロードした場合、否定演算子が優先されたようです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

checkベストアンサー

+11

多くの場合、 explicit operator bool() のみがあれば大丈夫のような気がするのですが、あえて 否定演算子のオーバーロードをする積極的な理由は何なのでしょうか?

私も質問者myoonさん、catsforepawさんと同意見です。ごく一部の特殊なユースケースを除いて、否定演算子(operator!)はオーバーロードすべきでないと考えます。

あえて演算子オーバーロードの意味があるとすれば;

  • Boost::sprit等のC++文法を拡張したDSL(Domain-Specific Language)ライブラリを実装するとき。驚き最小の原則に従い、独自DSLであっても!は“論理否定演算”を表すべきと思います。
  • 式テンプレート(ET; Expression Template)ライブラリを実装するとき。式!xを「xの論理否定演算を表す型」へと変換するため、明示的な演算子オーバーロードが必要となります。

例えば std::basic_ios では operator!() がオーバーロードされていますが、同時に、キャスト演算子 explicit operator bool() const;もオーバーロードされています。
たとえば、std::shared_ptr では!のオーバーロードはないようですね。

端的には 歴史的な経緯 で説明できます。外部QAサイトStackOverflowに "Why does std::basic_ios overload the unary logical negation operator?" という質問・回答がありました。

  • 古代のC++言語ではbasic_ios::operator!をオーバーロードする以外に、IOストリームを真偽値に変換する手段がありませんでした。つまりif (!s)のような否定形しか記述できなかったようです。
  • C++03の頃はbasic_ios::operator void*ユーザ変換関数が提供されており、if (s)if (!s) のいずれの書き方もできるようになりました。
  • C++11で言語仕様そのものが拡張され、 explicit operator bool() という表記が許されるようになりました。機能的には operator void*/operator! 両方をカバーし、かつプログラマの意図しない暗黙変換が起きるリスクも回避されます。
  • 一方で operator void* はプログラマを混乱させる恐れがあったため、このタイミングで削除されています。
  • std::shared_ptr はC++11で初めて標準ライブラリ入りしたため、最初から explicit operator bool() のみを提供すると考えられます。

自分でクラスを作るとき、!のオーバーロードを行うか否か?の指針が知りたいです。 

!に限らず、演算子オーバーロードの提供有無は慎重に設計すべきです。ISO C++公式FAQ(下記)やC++ Core Guideline C.over: Overloading and overloaded operators もご参考にどうぞ。

The previous FAQs tell me which operators I can override; but which operators should I override?

Bottom line: don’t confuse your users.
  Remember the purpose of operator overloading: to reduce the cost and defect rate in code that uses your class. If you create operators that confuse your users (because they’re cool, because they make the code faster, because you need to prove to yourself that you can do it; doesn’t really matter why), you’ve violated the whole reason for using operator overloading in the first place.


(おまけ情報です)

もし、論理演算子をオーバーロードする必用が在る場合でも「&&」「||」は短絡評価ができなくなるため危険です

C++11時点(かつC++14までの)仕様では認識通りです。C++17では「&&||論理演算子オーバーロードでも短絡評価が保証される」ように言語仕様が改善されました。とはいえ、C++コンパイラがどのように振舞うかはまだ微妙ですので、質問文中の認識でいたほうが安全だと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/30 02:50 編集

    式テンプレートでも「&&」を使いたければ、構文木作って評価のときに短絡評価すればいいですね。
    ありがとうございました。

    キャンセル

  • 2018/12/30 19:29

    コメント内であまりにも質問の内容とかけ離れた部分は削除しました。

    キャンセル

  • 2018/12/30 19:48

    皆様の回答すべてが私の役に立ちましたが、一番ぴったりして、かつ詳細な回答をいただきましたので、ベストアンサーとさせていただきました。ありがとうございます。

    キャンセル

+4

しかし、operator bool() があれば、たとえexplicitがついていても、否定演算子!のオペランドの場合は明示的なキャストなしにbool型に変換されます。

私もoperator boolをオーバーロードした場合は、あえてoperator !をオーバーロードすることはしないですね。質問者さん同様、そうしなければならない理由が思いつきません。

ただ、

例えば std::basic_ios では operator!() がオーバーロードされていますが

これに関しては、C++11より前のbasic_iosはexplicit operator boolではなくoperator void*がオーバーライドされていました(返されるnull以外のポインター値には意味がない!)。ポインター値でも暗黙の変換が行われて!演算子が使えますが、直感的に判りづらいので「!演算子も使えますよ」という意味でoperator !もオーバーライドしたのではないでしょうか。それがC++11でoperator boolのオーバーライドに変更された後も残されているだけのような気がします。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/29 18:56

    丁寧な回答、ありがとうございます。
    std::basic_ios について、以前は operator void* へオーバーロードされていたとは聞いていましたが、c++03より前のc++では、!を付けなければならなかったという他の方の回答がありましたので、やはり歴史的理由のようですね。
    オーバーロードする必用がないものをわざわざオーバーロードするのはやはりよくないと思います。
    特殊目的で行う場合はあるとしても。
    ありがとうございました。

    キャンセル

+3

Boost::spirit(C++のオーバーロードを駆使して、構文解析器を作る)のような、通常の演算でなく前衛的(あるいは変態的)にオーバーロードを「活用」する場合には、!も1つの演算子として動員することがあります(リファレンス)。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/28 19:41

    boostまで手を伸ばすとハマってしまう危険を感じて控えてましたが、boostもやはり通らなければならない道のようです。

    キャンセル

  • 2018/12/28 20:25

    前衛的(変態的)な使い方をする場合、ADLが余計な仕事をしてしまう危険がありますね。ADL firewall 等の概念があるようなので、必用になったらここで質問するまでもなくググれば分かると思いますgあ。

    キャンセル

+2

確かに、bool, bit, 整数では!を自分で作る価値はなさそうです。
しかし、複素数の直交ベクタを!として定義することはあるのでは?

x  =   a + bi ; // i*i = -1
-x => -a - bi ;
!x =>  b - ai ;

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/12/28 19:39

    回答ありがとうございます。
    もちろん、私の意図はbool型の場合ですが、おっしゃるような場合は定義が必用ですね。これはとてもわかり易い例でした。ありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.63%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • トップ
  • C++に関する質問
  • c++ のクラスで否定演算子!を積極的にオーバーロードする場合はどんな時なのでしょうか?