web-dev-qa-db-ja.com

switchとifステートメントが変換演算子で異なる動作をするのはなぜですか?

switchステートメントとifステートメントの変換演算子の動作が異なるのはなぜですか?

struct WrapperA
{
    explicit operator bool() { return false; }    
};

struct WrapperB
{
    explicit operator int() { return 0; }
};

int main()
{
    WrapperA wrapper_a;
    if (wrapper_a) { /** this line compiles **/ }

    WrapperB wrapper_b;
    switch (wrapper_b) { /** this line does NOT compile **/ }
}

コンパイルエラーはswitch quantity is not an integerifステートメント内では、boolとして完全に認識されます。 (GCC)

15
nyarlathotep108

構文はswitch ( condition ) statementで、

条件-整数型または列挙型、またはクラス型の式contextuallyimplicitly整数型または列挙型に変換できます。または、中括弧または等しいイニシャライザを使用して、そのような型の単一の非配列変数を宣言します。

cppreference。 から取得

つまり、整数または列挙型typeでのみ大文字と小文字を切り替えることができます。コンパイラーが暗黙的にWrapperを整数/列挙型に変換できるようにするには、明示的なキーワードを削除する必要があります。

明示的な指定子は、コンストラクターまたは変換関数(C++ 11以降)が暗黙的な変換を許可しないことを指定します

Wrapperをint型にキャストすることもできます。

@ acraig5075のアドレスを編集してコメント:

どの演算子が明示的で、どの演算子が暗黙的であるかに注意する必要があります。両方が暗黙的である場合、あいまいさがあるため、コードはコンパイルされません。

struct Wrapper
{
    operator int() { return 0; }
    operator bool() { return true; }    
};

source_file.cpp:関数「int main()」内:source_file.cpp:12:14:

エラー:「ラッパー」からのあいまいなデフォルトの型変換

スイッチ(w){

^ source_file.cpp:12:14:注:候補の変換

「Wrapper :: operator int()」と「Wrapper :: operator bool()」を含める

あいまいさを取り除く唯一の方法は、キャストを行うことです。

演算子の1つだけが明示的である場合は、switchステートメントで他の演算子が選択されます。

#include <iostream>
struct Wrapper
{
    explicit operator int() { return 0; }
    operator bool() { return true; }    
};

int main()
{
    Wrapper w;
    if (w) { /** this line compiles **/std::cout << " if is true " << std::endl; }
    switch (w) { 
        case 0:
            std::cout << "case 0" << std::endl;
            break;
        case 1:
            std::cout << "case 1" << std::endl;
            break;
    }
    return 0;
}

出力:

 if is true 
case 1

wは暗黙的に1true)に変換され(演算子intが明示的であるため)、ケース1が実行されます。

一方 :

struct Wrapper
{
    operator int() { return 0; }
    explicit operator bool() { return true; }    
};

Ouput:

 if is true 
case 0

演算子boolは明示的であるため、wは暗黙的に0に変換されました。

どちらの場合も、wがifステートメント内のブール値としてcontextuallyに評価されるため、ifステートメントはtrueです。

17
Clonk

this は、switchステートメントが受け入れられない理由を説明していると思いますが、ifステートメントは次のとおりです。

次の5つのコンテキストでは、宣言bool t(e);が整形式である場合、ブール型が予期され、暗黙の変換シーケンスが構築されます。つまり、explicit T::operator bool() const;などの明示的なユーザー定義の変換関数が考慮されます。そのような式eは、文脈的にboolに変換可能であると言われています。

  • if、while、forの式を制御する。
  • 論理演算子!、&&および||;
  • 条件演算子?:;
  • static_assert;
  • いいえ。
10
Marco Luzzara

変換演算子explicitの宣言は、その型への暗黙的な変換を防ぐために存在します。それがその目的です。 switchは、引数を暗黙的に整数に変換しようとします。したがって、explicit演算子は呼び出されません。これは予想される動作です。

予期しないのは、explicit演算子がifの場合に呼び出されることです。そしてそれによって物語をぶら下げる。

上記のルールを前提として、ifを介して型をテスト可能にする方法は、explicit以外の変換をboolに行うことです。問題は... boolは問題のあるタイプです。暗黙的に整数に変換できます。したがって、暗黙的にboolに変換可能な型を作成した場合、これは有効なコードです。

_void foo(int);
foo(convertible_type{});
_

しかし、これも無意味なコードです。 _convertible_type_を暗黙的に整数に変換するつもりはありません。テスト目的でブール値に変換したいだけです。

C++ 11以前では、これを修正する方法は「安全なブールイディオム」でしたが、これは複雑な問題であり、論理的な意味もありませんでした(基本的に、ブール値に変換可能なメンバーポインターへの暗黙的な変換を提供しましたが、整数や通常のポインタではありません)。

したがって、C++ 11では、explicit変換演算子を追加すると、boolの例外が発生しました。 explicit operator bool()がある場合、この型は、言語で定義された一連の場所の中で「コンテキストに応じてboolに変換」できます。これにより、explicit operator bool()が「ブール条件でテスト可能」を意味するようになります。

switchはそのような保護を必要としません。型をintの目的でswitchに暗黙的に変換可能にする場合、他の目的でも暗黙的にintに変換できない理由はありません。

3
Nicol Bolas

1つの答えは、ifswitchは標準が作成された方法であるため、動作が異なるということです。別の答えは、標準がそのように書かれた理由を推測するかもしれません。まあ、私は標準のifステートメントが特定の問題(boolへの暗黙の変換 問題があった )に対処するためにそのように動作すると思いますが、私はしたいです別の視点を採用します。

ifステートメントでは、条件はブール値でなければなりません。だれもがこのようにifステートメントについて考えているわけではありません。おそらく、言語に組み込まれたさまざまな便利さのためです。ただし、中核として、ifステートメントは「これを実行する」または「それを実行する」ことを知っている必要があります。 "はい、もしくは、いいえ"; trueまたはfalse-ブール値。この点で、ステートメントの条件内に何かを入れると、何かをboolに変換するように明示的に要求されます。

一方、switchステートメントは任意の整数型を受け入れます。つまり、他のどのタイプよりも優先される単一のタイプはありません。 switchの使用は、値を整数型に変換する明示的な要求と見なすことができますが、必ずしもintに明示的に要求する必要はありません。そのため、特定の変換を明示的に要求する必要がある場合、intへの変換を使用することは適切とは見なされません。

3
JaMiT

この動作の本当の理由はCにあると思いますが、それを説明する前に、C++の用語でそれを正当化しようと思います。

if/while/forステートメントは、任意のスカラー(整数、浮動小数点、またはポインター)、またはboolに変換可能なクラスインスタンスを取得することになっています。値がゼロに等しいかどうかを確認するだけです。この許容度を考えると、explicit演算子を使用して値をifステートメントに合わせるのはコンパイラにとって比較的無害です。

一方、switchは、整数とenumsでのみ意味があります。 switchまたはdoubleまたはポインターを使用しようとすると、同じエラーが発生します。これにより、離散ケース値の定義が容易になります。 switchステートメントは特に整数を参照する必要があるため、暗黙の変換を定義していないクラスインスタンスを使用するのはおそらく間違いです。

歴史的には、その理由はこれがCが行う方法です。

C++はもともとCとの下位互換性を保つことを目的としていました(ただし、C++は成功していません)。 Cは最近までブール型を保持していませんでした。 Cのifステートメントは、他の方法がなかったため、寛容でなければなりませんでした。 CはC++のように構造体からスカラーへの型変換を行いませんが、C++のexplicitメソッドの処理は、ifステートメントが両方の言語で非常に許容的であるという事実を反映しています。

Cのswitchステートメントは、ifとは異なり、離散値を処理する必要があるため、それほど寛容ではありません。

0
luther

コードには2つの問題があります。まず、変換演算子は、switchステートメントを処理するために明示的であってはなりません。次に、switch条件には整数型が必要であり、intboolはどちらもこのような型であるため、あいまいさが生じます。これらの2つの問題がないようにクラスを変更すると、switchは期待どおりにコンパイルおよび動作します。

クラスを変更する必要のない別の解決策は、明示的にキャストすることです(static_castintまたはboolの値を実行します。

0
navyblue