英語版
このページの英語版を見る

根拠:

Dのさまざまな設計上の決定の理由に関する質問が よく寄せられる。この回答は、それらの多くに対応している。

演算子オーバーロード

なぜ、operator+()、operator*()などと命名しないのか?

これはC++のやり方であり、 オーバーロードされた「+」を「operator+」で参照できるのは魅力的だ。問題は、

これはC++のやり方であり、 オーバーロードされた「+」を「operator+」で参照できるのは魅力的だ。問題は、 物事がうまく適合しないことだ。例えば、 比較演算子 <、<=、>、および >= がある。C++では、これら4つすべてをオーバーロードして 完全にカバーするには、これらすべてをオーバーロードする必要がある。D言語では、opCmp() 関数のみを定義すればよく、比較演算は 意味解析によってその関数から派生される。

さらに、数値ベースの型における二項演算子はほぼ一様に実装されているため、 opBinaryテンプレートを1つ用意するだけで、ユーザーが使用した演算子を簡単に組み込むことができる。 一方、C++では各演算子を個別に実装する必要がある。

さらに、数値型に基づくバイナリ演算子はほぼ一様に実装されているため、 opBinaryテンプレートを1つ用意するだけで、ユーザーが使用した演算子を"ミックスイン"することが可能である。 一方、C++では各演算子を個別に定義する必要がある。 たとえば:

import std.stdio;
struct MyInt
{
    int i;
    MyInt opBinary(string op)(in MyInt other) if(op == "+" || op == "-")
    {
        mixin ("return MyInt(i " ~ op ~ "other.i);");
    }
}
void main()
{
    MyInt a = MyInt(3), b = MyInt(1), c = a + b, d = a - b;
    writeln(a.i, ' ', b.i, ' ', c.i, ' ', d.i); // 3 1 4 2を表示する
}

演算子/()のオーバーロードも、逆の演算をオーバーロードする対称的な方法を提供しない。 例えば、

class A
{
    int operator/(int i);           // オーバーロード(a/i)
    static operator/(int i, A a)    // オーバーロード(i/a)
}

2番目のオーバーロードは逆のオーバーロードを行うが、 仮想関数にすることはできないため、 1番目のオーバーロードと非対称で混乱を招く。

グローバルに定義された演算子オーバーロード関数を許可しないのはなぜか?

演算子のオーバーロードは、引数としてオブジェクトを使用する場合のみ可能であるため、 論理的にはそのオブジェクトのメンバ関数として属する。 演算子のオーバーロードをグローバル関数として定義することはできない。 演算子のオーバーロードは、引数としてオブジェクトを使用する場合のみ可能であるため、 論理的にはそのオブジェクトのメンバ関数として属する。 演算子のオーバーロードをグローバル関数として定義することはできない。 演算子のオーバーロードは、引数としてオブジェクトを使用する場合のみ可能であるため、 論理的にはそのオブジェクトのメンバ関数として属する。 演算子のオーバーロードをグローバル関数として定義することはできない。 演算子のオーバーロードは、引数としてオブジェクトを使用する場合のみ可能であるため、 論理的にはそのオブジェクトのメンバ関数として属する。 演算子のオーバーロードをグローバル関数として定義することはできない。
  1. 演算子オーバーロードは、引数としてオブジェクトを使用してのみ行うことができるため、 論理的にはそのオブジェクトのメンバ関数として属することになる。
    class A { }
    class B { }
    int opAdd(class A, class B);
    
    opAdd() はクラスAまたはBのどちらに置くべきだろうか? 明らかなスタイル上の解決策は、 最初のオペランドのクラスに置くことだろう。
    class A
    {
        int opAdd(class B) { }
    }
    
  2. 演算子オーバーロードは通常、クラスのプライベートメンバーへのアクセスを必要とする。 それらをグローバルにすると、オブジェクト指向の カプセル化が破られる。
  3. (2) は、自動的に「フレンド」アクセス権を得る演算子オーバーロードによって対処できるが、このような異常な動作は、Dがシンプルであるという点と矛盾する。

ユーザー定義可能な演算子を許可すればよいのではないか?

これらは、さまざまなユニコード記号に新しい中置演算を追加する際に非常に役立つ。 問題は、Dではトークンが 意味解析から完全に独立していると想定されていることだ。 ユーザー定義演算子は、この前提を壊すことになる。

これらは、さまざまなユニコード記号に新しい中置演算子を追加する際に非常に役立つ。 問題は、Dではトークンが 意味解析から完全に独立していると想定されていることだ。 ユーザー定義の演算子はそれを壊してしまう。

なぜユーザー定義可能な演算子の優先順位を許可しないのか?

問題は、これが構文解析に影響を与えることだ。そして、構文 解析はDでは意味解析から完全に独立しているはずだ 。

問題は、これが構文解析に影響を与えること、そして構文 解析はDでは意味解析から完全に独立しているはずである ことだ。

opAdd、opDivなどの代わりに、__add__や__div__などの演算子名を使用しないのはなぜか?

__キーワードは、プロプライエタリな言語拡張を示すべきであり、 言語の基本部分を示すべきではない。

__キーワードは、独自仕様の言語拡張を示すべきであり、 言語の基本部分を示すものではない。

なぜバイナリ演算子オーバーロードを静的メンバにしないのか。そうすれば、 両方の引数が指定され、逆の操作に関する問題はもはや発生しない はずだ。

つまり、演算子オーバーロードは仮想関数にできないため、 おそらくは、実際の処理を行う別の仮想関数の周辺にシェルとして実装されることになるだろう。 これは結局、 見苦しいハックのように見えることになる。第二に、opC

つまり、演算子オーバーロードは仮想関数にできないため、 おそらくは実際の処理を行う別の仮想関数の周りにシェルを実装することになるだろう。 これは結局、 見苦しいハックのように見えることになる。次に、opCmp()関数はすでに Objectの演算子オーバーロードであり、いくつかの理由から仮想関数にする必要がある。 また、他の演算子オーバーロードの方法と非対称にすることは 不要な混乱を招くことになる。 "function"関数

プロパティ

D言語では、浮動小数点型の無限大を表現するために、なぜT.infinityのようなプロパティをコア言語に用意しているのか。 C++のようにライブラリで行うのではなく、std::numeric_limits<T>::infinity?

言い方を変えてみよう。「既存の言語で表現できるのであれば、 なぜそれをコア言語に組み込む必要があるのか?」 T.infinityに関して:
  1. コア言語に組み込むということは、コア言語が 浮動小数点無限大が何であるかを知っていることを意味する。テンプレート、型定義、 キャスト、constビットパターンなどのレイヤーで、それが何であるかを知らず、 誤用された場合に適切なエラーメッセージを表示することはまずない。
  2. (1) の副作用として、 定数畳み込みやその他の最適化において効果的に使用できない可能性が高い。
  3. テンプレートのインスタンス化、#include ファイルの読み込みなど、すべてに コンパイル時間とメモリコストがかかる。
  4. しかし、最悪なのは、単に無限大を得るために長大な処理が必要になることだ。 これは、「言語とコンパイラは IEEE 754 浮動小数点について何も知らない ので、信頼できない」ということを意味している。実際、 多くの優れた C++ コンパイラは 浮動小数点比較において NaN を正しく処理していない。 (Digital Mars C++ は正しく処理する。) C++98では、式やライブラリ関数におけるNaNやInfinityの処理については何も規定されていない。 したがって、動作しないと想定しなければならない。

まとめると、NaNと無限大をサポートするには、ビットパターンを返すテンプレートを用意する以上のことが必要である。 テンプレートは、コンパイラのコアロジックに組み込まれなければならない。 また、浮動小数点演算を扱うすべてのライブラリコードに浸透していなければならない。 そして、それは標準規格に含まれていなければならない。

例えば、op1またはop2、あるいはその両方がNaNである場合、次のようになる。

(op1 < op2)

と同じ結果にはなりません。

!(op1 >= op2)

NaNが正しく処理されている場合、

なぜstatic if(0) ではなく、 を使うのか?if (0)?

いくつかの制限がある。

いくつかの制限がある。

if (0) は新しいスコープを導入するが、静的な if(...) は導入しない。なぜこれが問題なのか? 条件付きで新しい変数を宣言したい場合、これは問題となる。
  1. if (0) は新しいスコープを導入するが、"static if(...)" は導入しない。なぜこれが問題なのか? 新しい変数を条件付きで宣言したい場合、これは問題となる。
    static if (...) int x; else long x;
    x = 3;
    
    一方、
    if (...) int x; else long x;
    x = 3;    // エラー、xは定義されていない
    
    false static if の条件分岐は、意味的には機能する必要はない。 例えば、どこか別の場所で条件付きでコンパイルされた宣言に依存している可能性がある。
  2. false static if の条件は、意味的に機能する必要はない。 例えば、それはどこか別の場所で条件付きでコンパイルされた宣言に依存する可能性がある。
    static if (...) int x;
    int test()
    {
        static if (...) return x;
        else return 0;
    }
    
    静的 if は宣言のみが許可されている場所にも記述できる。
  3. 静的 if は宣言のみが許可されている場所に表示される。
    class Foo
    {
        static if (...)
            int x;
    }
    
  4. static if" は新しい型の別名を宣言できる:
    static if (0 || is(int T)) T x;
    
    static ifstatic if