&&と||をオーバーロードする理由は実際にありますか?短絡しませんか?


137

短絡演算子の振る舞い&&とは、||プログラマのための素晴らしいツールです。

しかし、なぜオーバーロードするとこの動作が失われるのですか?演算子は関数の単なる構文糖であると理解していますが、演算子にboolはこのような振る舞いがあります。なぜこの単一の型に制限する必要があるのですか?この背後に技術的な理由はありますか?


1
@PiotrS。その質問がおそらく答えです。標準はこの目的のためだけに新しい構文を定義できると思います。おそらく似ている operator&&(const Foo& lhs, const Foo& rhs) : (lhs.bars == 0)
iFreilicht

1
@PiotrS .:トライステートロジックを検討します{true, false, nil}nil&& x == nil短絡する可能性がありますので。
MSalters 2014

1
@MSalters:検討してくださいstd::valarray<bool> a, b, c;、どのようにa || b || c短絡されると思いますか?
Piotr Skotnicki 14

4
@PiotrS .: 短絡が理にかなっているために、少なくとも1つの非ブール型が存在すると主張しています。私は、すべての非ブール型に対して短絡が理にかなっていると主張していません。
MSalters 2014

3
まだ誰もこれについて言及していませんが、下位互換性の問題もあります。この短絡が適用される状況を制限することに特別な注意が払われていない限り、このような短絡は、オーバーロードする、operator&&またはoperator||評価される両方のオペランドに依存する既存のコードを破壊する可能性があります。下位互換性を維持することは、既存の言語に機能を追加するときに重要です(または重要です)。
David Hammen 2014

回答:


151

すべての設計プロセスは、相互に互換性のない目標間の妥協をもたらします。残念ながら、&&C ++でオーバーロードされた演算子の設計プロセスは、混乱を招く最終結果を生み出しました。つまり、必要な機能(&&その短絡動作)が省略されています。

設計プロセスがどのようにしてこの不幸な場所に到達したかについての詳細は、私にはわかりません。ただし、後の設計プロセスでこの不快な結果がどのように考慮されたかを確認することは重要です。C#では、過負荷の&&演算子短絡です。C#の設計者はそれをどのように達成しましたか?

他の回答の1つは「ラムダリフティング」を示唆しています。あれは:

A && B

道徳的に同等のものとして実現することができます:

operator_&& ( A, ()=> B )

ここで、2番目の引数は遅延評価のメカニズムを使用するため、評価されると、式の副作用と値が生成されます。オーバーロードされた演算子の実装は、必要な場合にのみ遅延評価を行います。

これは、C#設計チームが行ったことではありません。(脇:ラムダリフティングはかかわらず、であるそれを行うための時間に来たときに私が何をしたかの式ツリー表現のを??。。特定の変換操作を必要とする作業を、遅延し実行することが詳細にしかし主要な余談になるとの記述十分を言うために:ラムダリフティング動作しますが、回避したかったほど十分に重いです。)

むしろ、C#ソリューションは問題を2つの別個の問題に分解します。

  • 右側のオペランドを評価する必要がありますか?
  • 上記の答えが「はい」の場合、2つのオペランドをどのように組み合わせるのでしょうか。

したがって、&&直接オーバーロードすることを違法にすることで問題は解決します。むしろ、C#では2つの演算子をオーバーロードする必要があり、それぞれがこれら2つの質問の1つに答えます。

class C
{
    // Is this thing "false-ish"? If yes, we can skip computing the right
    // hand size of an &&
    public static bool operator false (C c) { whatever }

    // If we didn't skip the RHS, how do we combine them?
    public static C operator & (C left, C right) { whatever }
    ...

(脇に:実際には3つあります。C#では、演算子falseが指定されている場合は演算子trueも指定する必要があります。これは、「これは本当ですか?」という質問に答えます。通常、そのような演算子を1つだけ指定する理由はないため、C#両方が必要です。)

次の形式のステートメントについて考えてみます。

C cresult = cleft && cright;

コンパイラは、この疑似C#を記述したと考えられるように、このためのコードを生成します。

C cresult;
C tempLeft = cleft;
cresult = C.false(tempLeft) ? tempLeft : C.&(tempLeft, cright);

ご覧のとおり、常に左側が評価されます。「偽っぽい」と判断された場合はその結果です。それ以外の場合は、右側が評価され、熱心なユーザー定義演算子&が呼び出されます。

||オペレータは、オペレータの呼び出し真と熱心なように、類似の方法で定義される|演算子。

cresult = C.true(tempLeft) ? tempLeft : C.|(tempLeft , cright);

-すべての4つの演算子を定義することでtruefalse&および|- C#は、あなたが言うできるだけでなくcleft && crightだけでなく、非短絡cleft & crightし、またif (cleft) if (cright) ...、とc ? consequence : alternativeしてwhile(c)、そしてそうで。

今、私はすべての設計プロセスが妥協の結果であると述べました。ここで、C#言語の設計者は短絡&&||正しい結果を得ることができましたが、そのためには2つではなく4つの演算子をオーバーロードする必要があり、混乱する人もいます。オペレーターのtrue / false機能は、C#で最もよく理解されていない機能の1つです。C ++ユーザーに馴染みのある賢明でわかりやすい言語を使用するという目標は、短絡させたいという欲求や、ラムダリフティングやその他の形式の遅延評価を実装しないという欲望によって反対されました。私は、それが合理的な妥協の位置だったと思うが、それがあることを認識することが重要であるである妥協の位置。ただ違う C ++の設計者が上陸したときよりも妥協の立場。

そのような演算子の言語設計の主題に興味がある場合は、C#がこれらの演算子をnull許容ブールで定義しない理由についての私のシリーズを読むことを検討してください。

http://ericlippert.com/2012/03/26/null-is-not-false-part-one/


1
@Deduplicator:あなたはまた、この質問と回答読んで興味があるかもしれません:stackoverflow.com/questions/5965968/...を
エリックリペット

5
この場合、妥協は正当化される以上のものだと思います。複雑なものは唯一のクラスライブラリの建築家が関係しなければならないものですが、この合併症と引き換えに、それが作る、消費ライブラリーより簡単かつ直感的なのを。
コーディグレイ

1
@EricLippert私は、エンビジョンがこの投稿を見て、それがあなただと思ったと言っていたと思います...そして、彼が正しかったのを見ました。彼your postは無関係であると言っていませんでした。His noticing your distinct writing style無関係です。
WernerCD 2014

5
マイクロソフトチームは、(1)C#で正しいことを行うために著しく優れた努力をしていること、および(2)正しくない回数を正しく実行したことに対して十分な評価を得ていません。
codenheim 2014

2
@Voo:あなたが暗黙の変換を実装することを選択した場合bool、あなたは使用することができます&&し、||実装することなく、operator true/falseあるいはoperator &/|問題なしC#で。問題は、に変換boolできない場合、または変換が望ましくない場合に正確に発生します。
Eric Lippert 2014

43

重要なのは、(C ++ 98の範囲内で)右側のオペランドがオーバーロードされた演算子関数に引数として渡されることです。そうすることで、すでに評価されます。operator||()またはoperator&&()コードがこれを回避することができる、またはできないことは何もありません。

元の演算子は関数ではないため異なりますが、言語の低レベルで実装されます。

追加の言語機能は、可能性があり、右の非評価オペランド構文的に行われている可能性が。ただし、これが意味的に役立つケースはほんの一部しかないため、問題はありませんでした。(と同様に? :、オーバーロードにはまったく使用できません。

(ラムダを標準化するのに16年かかりました...)

意味的な使用については、以下を考慮してください。

objectA && objectB

これは要約すると:

template< typename T >
ClassA.operator&&( T const & objectB )

への変換演算子を呼び出す以外に、ここでobjectB(不明なタイプの)boolをどのように処理したいのか、およびそれを言語定義の単語にどのように挿入するのかを考えてください。

そして、もしあなた boolへの変換を呼んでいるなら、まあ...

objectA && obectB

同じことをします、今それをしますか?では、なぜ最初からオーバーロードなのでしょうか。


7
論理エラーは、現在定義されている言語内で、別の方法で定義された言語の影響について推論することです。昔は、多くの初心者がそのことをしていました。「仮想コンストラクタ」。彼らをそのようなボックス思考から抜け出すには、非常に多くの説明が必要でした。とにかく、組み込み演算子の短絡により、引数の非評価についての保証があります。そのような保証は、短絡が定義されている場合、ユーザー定義のオーバーロードにも存在します。
乾杯とhth。-アルフ

1
@iFreilicht:基本的には、重複排除機能やPiotrと同じことを、違う言葉で言いました。編集した回答の要点について少し詳しく説明しました。この方法の方がはるかに便利でした。必要な言語拡張(ラムダなど)は最近まで存在していなかったため、とにかくそのメリットはごくわずかでした。担当者が1998年にコンパイラビルダーによってまだ行われていない何かを「気に入った」と何度か繰り返したが、それは裏目に出た。(を参照してくださいexport。)
DevSolar 2014

9
@iFreilicht:boolどちらのクラスの変換演算子もすべてのメンバー変数にアクセスでき、組み込み演算子で正常に機能します。いずれにせよ、boolへの変換以外は短絡回路の評価には意味がありません。構文的な観点からではなく、意味的な観点からこれにアプローチするようにしてください。どのようにそれについて取り組むかではなく、を達成しようとしているのでしょう
DevSolar 2014

1
考えられないことを認めざるを得ない。短絡が存在する唯一の理由は、ブール演算の時間を節約し、すべての引数が評価される前に式の結果を知ることができるためです。他のAND演算ではそう&&&はありません。そのため、同じ演算子ではありません。それを理解してくれてありがとう。
iFreilicht 2014

8
@iFreilicht:むしろ、短絡の目的は、左側の計算が右側の前提条件の真理を確立できるためです。 if (x != NULL && x->foo)速度ではなく安全のために短絡が必要です。
Eric Lippert 2014

26

機能は、考慮、設計、実装、文書化、および出荷する必要があります。

さて、それを考えて、なぜ今それが簡単なのか(そして、そうするのが難しいのか)見てみましょう。また、リソースは限られているため、追加すると他の要素が途切れる可能性があることにも注意してください(そのために何を差し控えますか?)。


理論には、C ++ 11(ラムダが導入されたとき、1979年に「クラス付きC」が開始されてから32年後)で、すべての演算子は1つの「マイナー」追加言語機能のみで短絡動作を許可できましたc ++ 98以降):

C ++は、必要に応じて許可される(前提条件が満たされる)まで評価を回避するために、遅延評価された引数(隠しラムダ)として引数に注釈を付ける方法が必要です。


その理論的な機能はどのように見えますか(どんな新しい機能も広く使用できるはずです)。

lazyfunction-argumentに適用されるアノテーションは、関数をファンクターを想定したテンプレートにし、コンパイラーに式をファンクターにパックさせます。

A operator&&(B b, __lazy C c) {return c;}

// And be called like
exp_b && exp_c;
// or
operator&&(exp_b, exp_c);

それは次のようにカバーの下に見えます:

template<class Func> A operator&&(B b, Func& f) {auto&& c = f(); return c;}
// With `f` restricted to no-argument functors returning a `C`.

// And the call:
operator&&(exp_b, [&]{return exp_c;});

ラムダは隠されたままであり、多くても一度だけ呼び出されることに注意してください。
これにより、共通部分式が削除される可能性が低くなることを除いて、パフォーマンスが低下することありません。


実装の複雑さと概念の複雑さ(他の機能の複雑さを十分に緩和しない限り、すべての機能が両方とも増加します)のほかに、別の重要な考慮事項である下位互換性を見てみましょう。

この言語機能はコードを壊すことはありませんが、それを利用してAPIを微妙に変更します。つまり、既存のライブラリでの使用はサイレントブレーク変更になります。

ちなみに、この機能は使いやすくはありますが、C#で分割&&||て2つの関数に分割し、それぞれを個別に定義するよりも厳密に強力です。


6
@iFreilicht:「機能Xが存在しないのはなぜですか?」という形式の質問 同じ答えがあります。機能が存在するためには、その機能が考えられ、優れたアイデアであると考えられ、設計、指定、実装、テスト、文書化され、エンドユーザーに出荷されている必要があります。それらのいずれかが発生しなかった場合、機能はありません。それらの1つは、提案された機能では発生しませんでした。どれが歴史的な研究問題であるかを見つける。それらのうちどれが一度も行われなかったことが気になる場合は、設計委員会の人々と話し始めてください。
Eric Lippert、2014

1
@EricLippert:そして、それがどの理由であるかによって、実装されるまで繰り返します:多分それはあまりにも複雑であると考えられ、誰も再評価をすることを考えていませんでした。または、再評価が以前に保持されたものとは異なる理由で拒否されました。(ところで:コメントの要点を追加しました)
Deduplicator

@Deduplicator式テンプレートでは、lazyキーワードもラムダも必要ありません。
Sumant

歴史的な余談として、元のAlgol 68言語には「手続き型」の強制(および、プロシージャ型ではなく、コンテキストが関数の型ではなく結果の型を必要とするときに暗黙的にパラメーターのない関数を呼び出すことを意味する)があったことに注意してください。つまり、「Tを返すパラメーターなしの関数」(Algol 68では「proc T」のスペル)の値を必要とする位置にあるT型の式は、指定された式を返す関数の本体に暗黙的に変換されます(暗黙のラムダ)。この機能は、1973年に改訂された言語で(手順解除とは異なり)削除されました。
Marc van Leeuwen

... C ++の場合、同様のアプローチは、&&「Tを返す関数へのポインター」型の引数を1つ取る演算子を宣言し、T型の引数式をラムダ式に暗黙的に変換できる追加の変換規則を宣言することです。これは構文レベルで行う必要があるため、通常の変換ではないことに注意してください。実行時にT型のを関数に変換しても、評価はすでに行われているため、役に立ちません。
マルクファンレーウェン2014

13

遡及的合理化では、主に

  • (新しい構文を導入せずに)短絡を保証するには、演算子を次のように制限する必要があります。 結果に変換可能な実際の最初の引数bool、および

  • 短絡は、必要に応じて他の方法で簡単に表現できます。


たとえば、クラスT&&and ||演算子が関連付けられている場合、式

auto x = a && b || c;

ここaで、、bおよびcはタイプの式でありT、次のように短絡して表すことができます

auto&& and_arg = a;
auto&& and_result = (and_arg? and_arg && b : and_arg);
auto x = (and_result? and_result : and_result || c);

またはおそらくより明確に

auto x = [&]() -> T_op_result
{
    auto&& and_arg = a;
    auto&& and_result = (and_arg? and_arg && b : and_arg);
    if( and_result ) { return and_result; } else { return and_result || b; }
}();

見かけ上の冗長性により、オペレーターの呼び出しによる副作用が保持されます。


ラムダの書き換えはより詳細ですが、そのより良いカプセル化により、そのような演算子を定義できます。

以下のすべての標準準拠(まだ少し影響があります)は完全にはわかりませんが、Visual C ++ 12.0(2013)およびMinGW g ++ 4.8.2で正常にコンパイルされます。

#include <iostream>
using namespace std;

void say( char const* s ) { cout << s; }

struct S
{
    using Op_result = S;

    bool value;
    auto is_true() const -> bool { say( "!! " ); return value; }

    friend
    auto operator&&( S const a, S const b )
        -> S
    { say( "&& " ); return a.value? b : a; }

    friend
    auto operator||( S const a, S const b )
        -> S
    { say( "|| " ); return a.value? a : b; }

    friend
    auto operator<<( ostream& stream, S const o )
        -> ostream&
    { return stream << o.value; }

};

template< class T >
auto is_true( T const& x ) -> bool { return !!x; }

template<>
auto is_true( S const& x ) -> bool { return x.is_true(); }

#define SHORTED_AND( a, b ) \
[&]() \
{ \
    auto&& and_arg = (a); \
    return (is_true( and_arg )? and_arg && (b) : and_arg); \
}()

#define SHORTED_OR( a, b ) \
[&]() \
{ \
    auto&& or_arg = (a); \
    return (is_true( or_arg )? or_arg : or_arg || (b)); \
}()

auto main()
    -> int
{
    cout << boolalpha;
    for( int a = 0; a <= 1; ++a )
    {
        for( int b = 0; b <= 1; ++b )
        {
            for( int c = 0; c <= 1; ++c )
            {
                S oa{!!a}, ob{!!b}, oc{!!c};
                cout << a << b << c << " -> ";
                auto x = SHORTED_OR( SHORTED_AND( oa, ob ), oc );
                cout << x << endl;
            }
        }
    }
}

出力:

000-> !! !! || 偽
001-> !! !! || 本当
010-> !! !! || 偽
011-> !! !! || 本当
100-> !! && !! || 偽
101-> !! && !! || 本当
110-> !! && !! 本当
111-> !! && !! 本当

ここで、各!!bang-bangはへの変換bool、つまり引数値のチェックを示しています。

コンパイラは同じことを簡単に行うことができ、さらに最適化できるため、これは可能な実装の実証であり、不可能の主張は一般に不可能の主張と同じカテゴリ、つまり一般的にはbollocksに分類する必要があります。


私はあなたの短絡の代用、特にあなたがおそらく得ることができる限り近い三元の代用を気に入っています。
iFreilicht 2014

あなたは-の短絡を逃しています-の&&ような追加の行が必要になるでしょうif (!a) { return some_false_ish_T(); }-そしてあなたの最初の箇条書きに:短絡はブールではなく、結果に変換可能なパラメーターについてです。
Arne Mertz 2014

@ArneMertz: "Missing"に関するコメントは明らかに無意味です。それが何であるかについてのコメント、はい、私はそれを知っています。変換はboolする必要がありませんショートを。
乾杯とhth。-Alf

Cheersandhth.-アルフ@行方不明についてのコメントは、あなたが短絡しなかったあなたの答えの最初の改正のためだった||が、ありません&&。もう1つのコメントは、最初の箇条書きの「ブール値に変換可能な結果に制限する必要がある」ということを目的としていました。「ブール値に変換可能なパラメーターに制限されている」というはずです。
Arne Mertz 2014

@ArneMertz:OK、再バージョン管理、すみません、編集が遅いです。再制限されます。制限されなければならないのは演算子の結果です。これは、bool式内の他の演算子の短い循環をチェックするために変換する必要があるためです。同様に、の論理ORの短絡をチェックするために、結果a && bをに変換するbool必要がありa && b || cます。
乾杯とhth。-Alf

5

tl; dr:かなり高いコスト(特別な構文が必要)と比較して、需要が非常に低い(誰が機能を使用するのか)ため、努力する価値はありません。

最初に頭に浮かぶのは、演算子のオーバーロードは関数を書くための空想的な方法にすぎないのに対し、演算子||とのブール型&&はbuitlinのものであるということです。コンパイラは表現しながら、ショート彼らに自由を持っていることをそれは意味x = y && znonbooleanとyzのような関数の呼び出しにつながることがありますX operator&& (Y, Z)。これは、奇妙な名前の関数の呼び出しであり、関数を呼び出す前に両方のパラメーターを評価する必要がある(短絡回路の適切と見なされるものを含む)ことy && zを書くための、派手な方法であることを意味します。operator&&(y,z)

ただし、演算子を変換して関数を呼び出し、その後にコンストラクターを呼び出す&&場合のように、演算子の変換をいくらか洗練させることが可能であると主張することもできます。newoperator new

技術的にはこれは問題になりません。短絡を可能にする前提条件に固有の言語構文を定義する必要があります。しかし、短絡回路の使用は、例に制限されるだろうYとconvetibleであるXか、あるいは実際に短絡(最初のパラメータだけから、すなわち計算結果)を行う方法の追加情報があることがありました。結果は次のようになります。

X operator&&(Y const& y, Z const& z)
{
  if (shortcircuitCondition(y))
    return shortcircuitEvaluation(y);

  <"Syntax for an evaluation-Point for z here">

  return actualImplementation(y,z);
}

operator||and をオーバーロードするoperator&&ことはめったにありません。なぜなら、書き込みがa && b実際には非ブールコンテキストで直感的であるケースはほとんどないからです。私が知っている唯一の例外は、たとえば組み込みDSL用の式テンプレートです。そして、これらの数少ないケースのほんの一握りだけが短絡評価から利益を得るでしょう。式テンプレートは、後で評価される式ツリーを形成するために使用されるため、通常は使用しません。したがって、常に式の両側が必要です。

要するに:コンパイラの作家でも基準もない筆者は百万では、ユーザ定義の上で短絡を持っていいだろうという考えを得るかもしれないという理由だけで、フープを介してジャンプし、追加の面倒な構文を定義し、実装する必要性を感じましたoperator&&し、operator||-ちょうど手作業でロジックを書くことほど簡単ではないという結論に達します。


コストは本当にそんなに高いのですか?Dプログラミング言語lazyでは、引数として指定された式を暗黙的に無名関数に変換するパラメーターを宣言できます。これにより、呼び出された関数に、その引数を呼び出すかどうかを選択できます。したがって、言語にすでにラムダがある場合、必要な追加の構文はごくわずかです。”疑似コード”:X and(A a、lazy B b){if(cond(a)){return short(a); } else {actual(a、b()); }}
ブラックジャック、2014

@BlackJackその遅延パラメータstd::function<B()>は、特定のオーバーヘッドを招くを受け入れることで実装できます。または、インライン化しても構わない場合は、それを作成し template <class F> X and(A a, F&& f){ ... actual(a,F()) ...}ます。そして、おそらく「通常の」Bパラメータでそれをオーバーロードして、呼び出し側が選択するバージョンを決定できるようにします。lazy構文がより便利になりますが、特定の性能のトレードオフがあることがあります。
Arne Mertz

1
std::function対の問題の1つは、lazy最初のものを複数回評価できることです。fooそのまま使用される遅延パラメーターfoo+fooは、まだ一度しか評価されません。
MSalters

「ショートサーキットの使用は、YがXに矛盾しない場合に制限される」...いいえ、それは、単独でX計算できる場合に制限されYます。非常に異なります。 std::ostream& operator||(char* a, lazy char*b) {if (a) return std::cout<<a;return std::cout<<b;}。「変換」の非常にカジュアルな使用法を使用している場合を除きます。
Mooing Duck 2014

1
@彼らはできる。しかし、ショートサーキットのロジックをoperator&&手動で書き出すこともできます。問題は、それが可能かどうかではありませんが、短い便利な方法がないのはなぜですか。
Arne Mertz 2014

5

ラムダスは、怠惰を導入する唯一の方法ではありません。C ++の式テンプレートを使用すると、遅延評価は比較的簡単です。キーワードは必要なくlazy、C ++ 98で実装できます。式ツリーはすでに上記で言及されています。表現テンプレートは貧弱な(しかし賢い)男性の表現ツリーです。トリックは、式をExprテンプレートの再帰的にネストされたインスタンス化のツリーに変換することです。ツリーは、構築後に個別に評価されます。

次のコードは、関数を提供して解放し、に変換できる限り、クラスの短絡演算子&&||演算子を実装します。コードはC ++ 14にありますが、アイデアはC ++ 98にも適用できます。ライブサンプルをご覧ください。Slogical_andlogical_orbool

#include <iostream>

struct S
{
  bool val;

  explicit S(int i) : val(i) {}  
  explicit S(bool b) : val(b) {}

  template <class Expr>
  S (const Expr & expr)
   : val(evaluate(expr).val)
  { }

  template <class Expr>
  S & operator = (const Expr & expr)
  {
    val = evaluate(expr).val;
    return *this;
  }

  explicit operator bool () const 
  {
    return val;
  }
};

S logical_and (const S & lhs, const S & rhs)
{
    std::cout << "&& ";
    return S{lhs.val && rhs.val};
}

S logical_or (const S & lhs, const S & rhs)
{
    std::cout << "|| ";
    return S{lhs.val || rhs.val};
}


const S & evaluate(const S &s) 
{
  return s;
}

template <class Expr>
S evaluate(const Expr & expr) 
{
  return expr.eval();
}

struct And 
{
  template <class LExpr, class RExpr>
  S operator ()(const LExpr & l, const RExpr & r) const
  {
    const S & temp = evaluate(l);
    return temp? logical_and(temp, evaluate(r)) : temp;
  }
};

struct Or 
{
  template <class LExpr, class RExpr>
  S operator ()(const LExpr & l, const RExpr & r) const
  {
    const S & temp = evaluate(l);
    return temp? temp : logical_or(temp, evaluate(r));
  }
};


template <class Op, class LExpr, class RExpr>
struct Expr
{
  Op op;
  const LExpr &lhs;
  const RExpr &rhs;

  Expr(const LExpr& l, const RExpr & r)
   : lhs(l),
     rhs(r)
  {}

  S eval() const 
  {
    return op(lhs, rhs);
  }
};

template <class LExpr>
auto operator && (const LExpr & lhs, const S & rhs)
{
  return Expr<And, LExpr, S> (lhs, rhs);
}

template <class LExpr, class Op, class L, class R>
auto operator && (const LExpr & lhs, const Expr<Op,L,R> & rhs)
{
  return Expr<And, LExpr, Expr<Op,L,R>> (lhs, rhs);
}

template <class LExpr>
auto operator || (const LExpr & lhs, const S & rhs)
{
  return Expr<Or, LExpr, S> (lhs, rhs);
}

template <class LExpr, class Op, class L, class R>
auto operator || (const LExpr & lhs, const Expr<Op,L,R> & rhs)
{
  return Expr<Or, LExpr, Expr<Op,L,R>> (lhs, rhs);
}

std::ostream & operator << (std::ostream & o, const S & s)
{
  o << s.val;
  return o;
}

S and_result(S s1, S s2, S s3)
{
  return s1 && s2 && s3;
}

S or_result(S s1, S s2, S s3)
{
  return s1 || s2 || s3;
}

int main(void) 
{
  for(int i=0; i<= 1; ++i)
    for(int j=0; j<= 1; ++j)
      for(int k=0; k<= 1; ++k)
        std::cout << and_result(S{i}, S{j}, S{k}) << std::endl;

  for(int i=0; i<= 1; ++i)
    for(int j=0; j<= 1; ++j)
      for(int k=0; k<= 1; ++k)
        std::cout << or_result(S{i}, S{j}, S{k}) << std::endl;

  return 0;
}

5

関連する真理値表の評価における「最適化」であるため、論理演算子の短絡は許可されます。これはロジック自体の機能であり、このロジックは定義されています。

実際に過負荷&&||短絡しない理由はありますか?

カスタムのオーバーロードされた論理演算子は、これらの真理値表のロジックに従う必要はありません

しかし、なぜオーバーロードするとこの動作が失われるのですか?

したがって、関数全体を通常どおりに評価する必要があります。コンパイラーはそれを通常のオーバーロードされた演算子(または関数)として扱う必要があり、他の関数と同様に最適化を適用できます。

人々はさまざまな理由で論理演算子に負荷をかけます。例えば; それらは、人々が慣れている「通常の」論理的なものではない特定のドメインで特定の意味を持つ場合があります。


4

短絡は「and」と「or」の真理値表によるものです。ユーザーが定義する操作をどのようにして知ることができ、2番目の演算子を評価する必要がないことをどのようにして知ることができますか?


コメントと@Deduplicatorsの回答で述べたように、言語機能を追加することで可能になります。今は動かないのはわかっています。私の質問は、そのような機能がないのには理由があるのか​​ということでした。
iFreilicht 2014

それは確かに複雑な機能です。ユーザーの定義を推測する必要があるためです。
nj-ath

何についての: (<condition>)第二引数が評価されていない時に条件を指定するオペレータ宣言の後?
iFreilicht 2014

@iFreilicht:別の単項関数本体がまだ必要です。
MSalters 2014

3

しかしboolの演算子はこの振る舞いをします、なぜそれをこの単一の型に制限する必要があるのですか?

私はこの部分に答えたいだけです。その理由は、組み込みの式&&||式が、多重定義された演算子のように関数で実装されていないためです。

コンパイラーが特定の式を理解するために、短絡ロジックを組み込むのは簡単です。他の組み込み制御フローと同じです。

ただし、演​​算子のオーバーロードは代わりに関数で実装され、特定のルールがあります。その1つは、引数として使用されるすべての式が、関数が呼び出される前に評価されることです。明らかに異なるルールを定義することもできますが、それはより大きな仕事です。


1
任意の配慮はの質問に与えられた場合、私は疑問に思うかどうかの過負荷&&||および,許可すべきですか?C ++にオーバーロードが関数呼び出し以外の動作を許可するメカニズムがないという事実は、これらの関数のオーバーロードが他に何もできない理由を説明していますが、そもそもそれらの演算子がオーバーロード可能である理由を説明していません。本当の理由は、彼らがあまり考えずに演算子のリストに投げ込まれたということだけです。
スーパーキャット2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.