1

これを読んだ人なら誰でもデフォルトの引数について知っていることを願っています:

void setCase (string &str, int form = UPPERCASE)
{
    for (char &c : str)
        c = (form == UPPERCASE ? c & ~0x20 : c | 0x20); //this bit differentiates english uppercase and lowercase letters
}

int main()
{
    string s1 = "HeLlO", s2 = s1, s3 = s1;
    setCase (s1, UPPERCASE); //now "HELLO"
    setCase (s2, LOWERCASE); //now "hello"
    setCase (s3); //now "HELLO" due to default argument
}

デフォルトの引数を使用することの 1 つの欠点は、デフォルトの引数をリストの最後から開始する必要があることです。場合によっては、引数を並べ替えて、使用するのがかなりばかげているように見えることがあります。これを回避するには、個別のオーバーロードを作成する必要があります。

例として、クラス名、タイトル、またはその両方を介してウィンドウを検索するウィンドウ API 関数FindWindowを取り上げます。

HWND WINAPI FindWindow( //returns handle to window
  __in_opt  LPCTSTR lpClassName, //param equivalent to const TCHAR *, classes are like templates for windows
  __in_opt  LPCTSTR lpWindowName //the text that appears on the title bar (for normal windows, for things like buttons, it's what text is on the button)
);

これをラップするために、デフォルトの検索オプションをタイトルにしたいかもしれません。これを実装するには、3 つの理想的な方法があります (他のラッピング手法が使用されていると仮定します)。完璧な解決策は、次のようになる可能性があります。

Window FindWindow (LPCTSTR className = 0, LPCTSTR windowName){...}

2 番目の解決策は、関数の 1 つのバージョンをオーバーロードしてタイトルのみを受け入れ、別のバージョンを両方を受け入れることです。3 つ目は、パラメーターの順序を切り替えることです。

2 番目の主な問題は、長いリストの場合、リストが大きくなるにつれて、オーバーロードによるスペースの量が非常に大きくなる可能性があることです。3 番目の主な問題は、以前にこの関数を使用していた人は、最初にクラス名を指定することに慣れていることです。これは、通常の C++ 関数にも当てはまります。パラメータは自然な順序を持つ傾向があります。

もちろん、最初の解決策の主な問題は、C++ 言語でサポートされていないことです。私の質問は
、これが将来利用可能になる可能性はありますか?

たとえば、コンパイラは必要に応じて適切なオーバーロードを自動的に生成できますか?

4

1 に答える 1

5

ばかばかしいほどありそうもない。コーナーケースが多いです現在のルールは非常に簡単で、「すべてのデフォルト引数は引数リストの最後にある必要があります」というものです。新しいルールは、「後方互換性を維持する必要がある場合を除いて、省略されたデフォルト引数の組み合わせがあいまいになってはならない」です。さらに悪いことに、これは定義の時点でテストできるルールでさえありません。なぜなら、C++ は現在、オーバーロードされた関数に対してそれさえ行っていないからです。たとえば、次の 2 つの関数定義を取り上げます。

void foo();
void foo(int x = 0);

最初に呼び出されると期待するのは不合理ですが、これらは完全に合法です。呼び出しfoo()はあいまいです。ここで、デフォルトの引数が最後に来る必要がない C++ の架空のバージョンを考えてみましょう。

void foo(int x = 0, int y = 0);

コールは何をするのfoo(1)ですか?下位互換性を保つには、 を呼び出す必要がありますfoo(1, 0)。この関数にはそのような問題がないため、興味深いです。

void bar(const char* a = 0, int b = 0);

その関数への正当な呼び出しを次に示します。

bar("foo");
bar(1);
bar("foo", 1);
bar();

したがって、この関数は、、およびfooの 3 つのバージョンのみを生成します。しかし、これもデフォルトの引数が 2 つあるため、4 つ生成されます。(そして、それらは明確ではありません:はあいまいな呼び出しです。) まあ、おそらく、標準の複雑な言語を使用して作業することができます。しかし今、この関数を考えてみましょう:foo()foo(int)foo(int, int)foo(0)

struct A;
struct B;
A some_A();
B some_B();
void baz(const A& a = some_A(), const B& b = some_B());

現在、生成されたバージョンの数は、ユーザー定義型の変換に依存しており、関数の定義では表示されない場合もあります。C++ の現在のバージョンでは、 への呼び出しは常にインスタンスをbaz(B())に変換しようとし、それ以外の場合は失敗します。さて、誰かがあなたのインスタンスを 2 番目の引数に渡すことを合理的に期待することができます。また、既存のコードを壊したくない場合を除いて、デフォルト引数ユートピアの世界での呼び出しがあいまいであると考えることさえできません。BABbazbaz()baz(const A&)baz(const B&)baz(const A&, const B&)baz(B())

変換はまた、「異なる型のデフォルト以外の引数で区切られたデフォルト引数」のような比較的単純なケースでさえ厄介なものにします。たとえば、次のようになります。

void quux(A* a = nullptr, B* b, C* c = nullptr);

quux(B*)完全に明確です: 、quux(A*, B*)quux(B*, C*)、およびとして呼び出し可能quux(A*, B*, C*)です。どちらから継承するか (またはその逆)をA継承しない限り。確かに、これはオーバーロードの解決が直面しなければならない問題と同じですが、これまでのところ、既定の引数は完全に明確であり、現在は微妙な問題の泥沼に陥っています。BC

万人を満足させる一貫した解決策を見つけたとしても、簡潔に説明することはほとんど不可能であり、おそらく純損失になるでしょう。

于 2012-04-05T03:19:16.350 に答える