どの言語にも単項ブールトグル演算子がありますか?


141

ですから、これは理論的な問題です。C ++とそれに直接(非)基づいている言語(Java、C#、PHP)には、次のようなほとんどの2項演算子の結果を最初のオペランドに割り当てるためのショートカット演算子があります。

a += 3;   // for a = a + 3
a *= 3;   // for a = a * 3;
a <<= 3;  // for a = a << 3;

しかし、ブール式を切り替えたいときは、いつも次のように書いています

a = !a;

これaは、長い式のように煩わしくなります。

this.dataSource.trackedObject.currentValue.booleanFlag =
    !this.dataSource.trackedObject.currentValue.booleanFlag;

(そうです、デメテルの法則、私は知っています)。

だから私は、思っていた単項ブールトグルオペレータと任意の言語があり、私は省略することができるようになるa = !aための表現を繰り返すことなくa、たとえばは、

!=a;  
// or
a!!;

私たちの言語に適切なブール型(boolC ++のような)があり、それaがその型(つまりCスタイルではない)であると仮定しましょうint a = TRUE

文書化されたソースを見つけることができる場合は、たとえば、C ++の設計者がbool組み込み型になったときにそのような演算子を追加することを検討したかどうか、またそうであれば、なぜそれを拒否したのかを知りたいと思います。


(注:私は何人かの人々は割り当てが使用するべきではないという意見であることを知っている =し、それ+++=便利事業者が、設計上の欠陥ではありません。レッツちょうど私が彼らと満足していると仮定して、彼らはboolsに延長しないだろう理由についてフォーカス)。


31
関数についてはどうですか?void Flip(bool& Flag) { Flag=!Flag; }長い式を短くします。
18

72
this.dataSource.trackedObject.currentValue.booleanFlag ^= 1;
KamilCuk 2018

13
コードを簡略化するために、参照に長い式を割り当てることができます
Quentin 2

6
@KamilCukこれはうまくいくかもしれませんが、タイプを混同しています。ブールに整数を割り当てます。
18

7
@ user463035818あります*= -1が、なんらかの理由でより直感的です^= true
CompuChip 2018

回答:


15

この質問は、純粋に理論的な観点からは確かに興味深いものです。単項で変化するブール値のトグル演算子が役立つかどうか、または多くの言語がそれを提供しないことを選択した理由を別にして、実際に存在するかどうかを調べるために探求しました。

TL; DRは明らかにそうではありませんが、Swiftを使用すると実装できます。方法だけを確認したい場合は、この回答の下部までスクロールできます。


さまざまな言語の機能を(迅速に)検索した後、この演算子を厳密な変更インプレース操作として実装している言語はありません(見つかった場合は修正してください)。したがって、次のことは、言語を作成できる言語があるかどうかを確認することです。これに必要なのは次の2つです。

  1. 関数で(単項)演算子を実装できる
  2. 上記の関数が参照渡しの引数を持つことを許可します(これにより、引数を直接変更できるようになります)

多くの言語は、これらの要件のいずれかまたは両方をサポートしないため、すぐに除外されます。Javaは、演算子のオーバーロード(またはカスタム演算子)を許可していません。さらに、すべてのプリミティブ型は値で渡されます。Goは、オペレーターのオーバーロード(ハックを除く)をまったくサポートしていません。Rustは、カスタム型の演算子のオーバーロードのみを許可します。Scalaでこれをほぼ達成できます。これにより、非常に独創的な名前の関数を使用し、括弧も省略できますが、残念ながら参照渡しはありません。Fortranは、カスタムオペレーターを許可するという点で非常に近づいていますが、特に、それらがinoutを持つことを禁止しています パラメータ(通常の関数とサブルーチンで許可されています)。


ただし、必要なすべてのボックスをチェックする言語が少なくとも1つあります。それはSwiftです。一部の人々は次の.toggle()メンバー関数にリンクしていますが、実際にinout引数をサポートする独自の演算子を作成することもできます。見よ:

prefix operator ^

prefix func ^ (b: inout Bool) {
    b = !b
}

var foo = true
print(foo)
// true

^foo

print(foo)
// false


3
はい。ただし、質問では、メンバー関数ではなく、演算子を具体的に求めました。
Lauri Piispanen

4
「Rust
もし

3
@Boiethiosありがとう!Rust用に編集-ただし、カスタムタイプのオーバーロードされた演算子のみを許可するため、サイコロは使用できません。
Lauri Piispanen

3
@LauriPiispanenルールはそれよりも一般的であり、孤立したルールのため、FYI
Boiethios

143

ブールビットを切り替える

...これによりa = !a a ... の表現を繰り返さなくても省略できます

このアプローチは実際には純粋な「変異フリップ」演算子ではありませんが、上記の基準を満たしています。式の右側には変数自体は含まれません。

ブールXOR割り当て(例えば持つ任意の言語は^=)言って、変数の現在の値を反転できるようになるaまでXORの割り当てによって、true

// type of a is bool
a ^= true;  // if a was false, it is now true,
            // if a was true, it is now false

以下のコメントで@cmasterが指摘しているように、上記は整数型やポインタaboolではなく、型であると想定しています。aが実際に他の何かである場合(たとえばbool、「」または「偽」の値に評価されない0b1ものであり0b0、それぞれビット表現がまたはではない)、上記は成立しません。

具体的な例として、Javaはこれが明確に定義されており、サイレント変換の対象にならない言語です。以下から@Boannのコメントを引用:

Javaでは、^^=明示的にブール値のためにと整数の振る舞いを定義している(15.22.2。ブール論理演算子&^、および|)、オペレータの両側のいずれかをブール値でなければならないところ、または両側は整数でなければなりません。これらのタイプの間でサイレント変換はありません。したがってa、が整数として宣言されている場合は、黙って誤動作することはなく、コンパイルエラーが発生します。だから、a ^= true;安全で、Javaで明確に定義されています。


迅速: toggle()

Swift 4.2以降、次の進化の提案が受け入れられ、実装されました。

これにより、Swift toggle()Bool型にネイティブ関数が追加されます。

toggle()

ブール変数の値を切り替えます。

宣言

mutating func toggle()

討論

このメソッドを使用して、ブール値をtrueto falseまたはfrom falseto に切り替えますtrue

var bools = [true, false]

bools[0].toggle() // bools == [false, false]

これはそれ自体は演算子ではありませんが、ブール値の切り替えのための言語固有のアプローチを可能にします。


3
コメントは拡張ディスカッション用ではありません。この会話はチャットに移動さました
サミュエルLiew

44

C ++では、演算子の意味を再定義することで枢機卿の罪を犯すことが可能です。これを念頭に置いて、ADLの少しだけを考えると、ユーザーベースで騒乱を解き放つために必要なことは次のとおりです。

#include <iostream>

namespace notstd
{
    // define a flag type
    struct invert_flag {    };

    // make it available in all translation units at zero cost
    static constexpr auto invert = invert_flag{};

    // for any T, (T << invert) ~= (T = !T)    
    template<class T>
    constexpr T& operator<<(T& x, invert_flag)
    {
        x = !x;
        return x;
    }
}

int main()
{
    // unleash Hell
    using notstd::invert;

    int a = 6;
    std::cout << a << std::endl;

    // let confusion reign amongst our hapless maintainers    
    a << invert;
    std::cout << a << std::endl;

    a << invert;
    std::cout << a << std::endl;

    auto b = false;
    std::cout << b << std::endl;

    b << invert;
    std::cout << b << std::endl;
}

予想される出力:

6
0
1
0
1

9
「演算子の意味を再定義することの枢機卿の罪」—私はあなたが誇張していると思いますが、一般に既存の演算子に新しい意味を再割り当てすることは本当に枢機卿の罪ではありません
Konrad Rudolph

5
@KonradRudolphこれが私が言うことはもちろん、演算子は通常の算術的または論理的意味に関して論理的意味を持つべきだということです。2つの文字列の 'operator + `は論理的です。なぜなら、連結は(私たちの目から見て)加算や累積に似ているからです。operator<<STLでの元の乱用により、「ストリーミング」を意味するようになりました。当然、「変換関数をRHSに適用する」という意味ではありません。これは、本質的に私がここで再利用したものです。
Richard Hodges

6
申し訳ありませんが、これは良い議論ではないと思います。まず、文字列の連結は、追加とはまったく異なるセマンティクスを持っています(可換性すらありません!)。第2に、ロジックによって「<<シフトアウト」演算子ではなく、ビットシフト演算子があります。再定義の問題は、それが演算子の既存の意味と矛盾することではなく、単純な関数呼び出しに値を追加しないことです。
Konrad Rudolph、

8
<<悪いオーバーロードのようです。私はそうし!invert= x;ます;)
Yakk-Adam Nevraumont

12
@ Yakk-AdamNevraumont LOL、これで私が始めました:godbolt.org/z/KIkesp
Richard Hodges

37

アセンブリ言語が含まれている限り...

前方へ

INVERT ビットごとの補数。

0= 論理的な(真/偽)補数の場合。


4
議論の余地があります。すべてのスタック指向言語では、単項notは「トグル」演算子です:-)
Bergi

2
確かに、OPは従来の割り当てステートメントを持つCのような言語について明確に考えているので、少し横向きの答えです。このような横向きの答えは、読者が考えていなかったプログラミングの世界の一部を示すためだけに価値があると思います。(「天と地のホレイショには、私たちの哲学で夢見ているよりも多くのプログラミング言語があります。」
ウェインコンラッド

31

いくつかの小さなマイクロコントローラーの方言でサポートされているタイプboolをインクリメントまたはデクリメントするように、C99 をデクリメントすると望ましい効果が得られますbit(これは、私が観察したものから、ビットをシングルビット幅のビットフィールドとして扱うため、すべての偶数が0およびすべてに切り捨てられます奇数から1)。私はの大ファンではないので、私は特に一部では、このような使用をお勧めしませんbool型のセマンティクス[IMHO、タイプがいることを指定していなければならないboolかのように読んだときに0または1以外の値が格納されているが振る舞うこと未指定の(必ずしも一貫しているとは限らない)整数値を保持します。プログラムが0または1として認識されていない整数値を保存しようとしている場合は、!!最初にそれを使用する必要があります]。


2
C ++はbool C ++ 17まで同じことを行いました。
デイビスヘリング

31

2
これがx86アセンブリであると仮定すると、これはopが考えていたものとはまったく思えません。例en.wikibooks.org/wiki/X86_Assembly/Logic ; here edx would be 0xFFFFFFFE because a bitwise NOT 0x00000001 = 0xFFFFFFFE
BurnsBA

8
代わりにすべてのビットを使用できます:NOT 0x00000000 = 0xFFFFFFFF
Adrian

5
ええ、そうです、私はブール演算子とビット演算子の違いを引き出そうとしていました。opが質問で言うように、言語が明確に定義されたブール型を持っていると想定します:「(したがって、Cスタイルの整数a = TRUEはありません)
BurnsBA 2018

5
@BurnsBA:確かに、アセンブリ言語には明確に定義された真偽値のセットが1つもありません。コードゴルフについては、さまざまな言語でのブール問題に対してどの値を返すことができるかについて多くのことが議論されてきました。 asmにはif(x)構成要素がないため、SIMD比較(felixcloutier.com/x86/PCMPEQB:PCMPEQW:PCMPEQD.html)のように、0 / -1をfalse / trueとして許可することは完全に合理的です。しかし、これを他の値に使用すると、これが壊れるのは正しいことです。
Peter Cordes

4
PCMPEQWが0 / -1になる単一のブール表現を持つことは困難ですが、SETccは0 / + 1になります。
Eugene Styer

28

これだけに基づいて言語を選択するつもりはないと思います:-)いずれにせよ、C ++では次のような方法でこれを行うことができます。

inline void makenot(bool &b) { b = !b; }

たとえば、次の完全なプログラムを参照してください。

#include <iostream>

inline void makenot(bool &b) { b = !b; }

inline void outBool(bool b) { std::cout << (b ? "true" : "false") << '\n'; }

int main() {
    bool this_dataSource_trackedObject_currentValue_booleanFlag = false;
    outBool(this_dataSource_trackedObject_currentValue_booleanFlag);

    makenot(this_dataSource_trackedObject_currentValue_booleanFlag);
    outBool(this_dataSource_trackedObject_currentValue_booleanFlag);

    makenot(this_dataSource_trackedObject_currentValue_booleanFlag);
    outBool(this_dataSource_trackedObject_currentValue_booleanFlag);
}

これは期待どおりに出力されます:

false
true
false

2
あなたもしたいreturn b = !bですか?誰かが読んfoo = makenot(x) || yだり、単純に読んだりfoo = makenot(bar)すると、それmakenotが純粋な関数であり、副作用がないと仮定するかもしれません。より大きな式の中に副作用を埋め込むことはおそらく悪いスタイルであり、非voidの戻り値型の唯一の利点はそれを有効にすることです。
Peter Cordes

2
@MatteoItaliaはを提案 template<typename T> T& invert(T& t) { t = !t; return t; }しています。テンプレート化boolされているboolオブジェクトは、非オブジェクトで使用された場合に変換されます。IDKが良いか悪いか。おそらく良い。
Peter Cordes


21

Visual Basic.Netは、拡張メソッドを介してこれをサポートします。

次のように拡張メソッドを定義します。

<Extension>
Public Sub Flip(ByRef someBool As Boolean)
    someBool = Not someBool
End Sub

そして、次のように呼び出します。

Dim someVariable As Boolean
someVariable = True
someVariable.Flip

したがって、元の例は次のようになります。

me.DataSource.TrackedObject.CurrentValue.BooleanFlag.Flip

2
これは作成者が求めていた省略形として機能しますが、もちろん、Flip()=とを実装するには2つの演算子を使用する必要がありますNot。同等のC#コードはコンパイルされないのに対し、呼び出しのSomeInstance.SomeBoolean.Flip場合SomeBooleanはプロパティであっても機能することを学ぶのは興味深いことです。
BACON、

14

Rustでは、独自のトレイトを作成して、そのNotトレイトを実装するタイプを拡張できます。

use std::ops::Not;
use std::mem::replace;

trait Flip {
    fn flip(&mut self);
}

impl<T> Flip for T
where
    T: Not<Output = T> + Default,
{
    fn flip(&mut self) {
        *self = replace(self, Default::default()).not();
    }
}

#[test]
fn it_works() {
    let mut b = true;
    b.flip();

    assert_eq!(b, false);
}

^= true推奨どおりに使用することもできます。Rustの場合はfalse、CまたはC ++のような「偽装された」整数ではないため、これを実行しても問題はありません。

fn main() {
    let mut b = true;
    b ^= true;
    assert_eq!(b, false);

    let mut b = false;
    b ^= true;
    assert_eq!(b, true);
}


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.