web-dev-qa-db-ja.com

文字列挙型の値の誤った割り当て

私は列挙型で遊んでいて、いくつかの例を再現しようとしました これから ページ。最初の例は意図したとおりに機能しましたが、次のコードでいくつかの興味深い結果が得られました。

#include <iostream>

enum num : char {
    zero = '0',
    one = '1',
    two = '2',
    three = '3',
    four = '4',
    five = '5',
    six = '6'
};

int main()
{
    const char two = '2';
    std::cout << two << std::endl;
    std::cout << num::two;
    return 0;
}

出力は次のとおりです。

2
50

両方の結果が同じになると思っていましたが、num::two他の値を出力するようです。また、この値は変更されません(50)、これはランダム/ガベージ値ではないと思います。理解できない文字/整数解析が行われているのでしょうか。これが ideoneリンク です。

このように割り当てることで、それを機能させることができることを私は知っていますzero = 0、一重引用符なしで機能します。ただし、舞台裏で何が起こっているのか、一重引用符の割り当てを介して印刷できる1桁以外の値をどのように制御できるのかを知りたいです。

24

これは実際にはcharオーバーロードに移動するはずです。残念ながら、問題のコンパイラはどれも実装していません DR 1601

[conv.prom]/4

基になる型が固定されているスコープ外の列挙型のprvalue([dcl.enum])は、基になる型のprvalueに変換できます。

これは、numcharに昇格できることを意味します。

さらに、汎整数拡張をその基になる型に適用できる場合は、基になる型が固定されているスコープなし列挙型のprvalueを、プロモートされた基になる型のprvalueに変換することもできます。

したがって、numintに昇格させることもできます。

関連する候補者は次のとおりです。

template <class Traits >
basic_ostream<char,Traits>& operator<<( basic_ostream<char,Traits>& os,
                                        char ch );
template<class charT, class Traits>
basic_ostream<charT, Traits>& basic_ostream<charT, Traits>::operator<<(int);

どちらの候補者にとっても、最初の引数はID変換であり、2番目の引数は昇進です。 numからcharnumからintの両方にプロモーションランクがあります。

DR1601より前では、これらは同等に優れているため、テンプレート/非テンプレートタイブレーカーが付属しています。最初のものは関数テンプレートです。 2番目の関数は単純なメンバー関数なので、2番目の関数が優先されます。

DR1601は、次のようなルールを追加しました。

基になる型が基になる型に固定されている列挙型をプロモートする変換は、2つが異なる場合、プロモートされる基になる型に昇格する変換よりも優れています。

これは、numからcharnumからintよりも優れていることを意味します。したがって、最初のオーバーロードがより適切に一致するため、選択する必要があります。

20
T.C.

C++標準(4.5インテグラルプロモーション)による

4基になる型が固定されている(7.2)スコープのない列挙型のprvalueは、基になる型のprvalueに変換できます。 さらに、汎整数拡張をその基になる型に適用できる場合、基になる型が固定されているスコープなし列挙型のprvalueも、昇格された基になる型のprvalueに変換できます

したがって、汎整数拡張が適用され、int型のオブジェクトの演算子<<が呼び出されます。

14

enum num : charと言うときは、numcharに関して内部的に実装されているが、それでも自動的に整数値に変換できるという事実を表します。つまり、not必ずしもchar

あなたが引用するページが言うように:

スコープなしの列挙型の値は、暗黙的に整数型に変換できます。

の組み合わせに関するC++標準の表現の問題に関する興味深い議論については、 charの基になる型が固定された列挙型の値がfct(char)ではなくfct(int)に解決されるのはなぜですか? を参照してください。汎整数拡張と固定基礎型。

いずれにせよ、プライベートcharメンバー変数とパブリックint変換演算子を持つクラスのようなこの全体を想像することができます。

// very similar to your enum:
class num {
private:
    char c;

public:
    num(char c) : c(c) {}
    operator int() const {
        return c;
    }
};

num two { '2' };
std::cout << two; // prints 50

型の安全性を高め、std::cout行をコンパイルエラーにするには、enumenum classに変換します。

enum class num : char

これも上記の想像上のclass numに似ていますが、変換演算子がありません。

numのインスタンスをstd::coutにフィードすると、あなたはnumのクライアントであり、出力形式が内部のchar実装を考慮に入れます。

出力形式をより細かく制御するには、代わりに他のカスタムタイプと同様に行い、operator<<に対してstd::ostreamをオーバーロードする必要があります。例:

#include <iostream>

enum class num : char {
    zero = '0',
    one = '1',
    two = '2',
    three = '3',
    four = '4',
    five = '5',
    six = '6'
};

std::ostream& operator<<(std::ostream& os, num const& n)
{
    switch (n)
    {
        case num::zero: os << "Zero"; break;
        case num::one: os << "One"; break;
        case num::two: os << "Two"; break;
        case num::three: os << "Three"; break;
        // and so on
    }
    return os;
}

int main()
{
    std::cout << num::two; // prints "Two"
}

もちろん、列挙型インスタンスの特定のchar値は今ではかなり役に立たなくなっているので、それらを完全に取り除くこともできます。

enum class num : char {
    zero,
    one,
    two,
    three,
    four,
    five,
    six
};

これは奇妙に思われるかもしれませんが、0から6までの一般的な数値だけを表す列挙型は現実的なユースケースではないことに注意してください。

10
Christian Hackl

2つは2つの異なる演算子のオーバーロードを呼び出すため:

  • 最初は非メンバーを呼び出しますoperator<< ために std::ostreamおよびchar。文字を印刷します。

  • 2番目の例では、メンバーをoperator<< for intは、他の回答で説明されているように、整数の昇格によるものです。

7
bolov

その理由は、enum : charcharと同じではないためです(これはまさに私たちが望んでいることです-割り当て互換であっても、列挙型が他のタイプと同じになることは望ましくありません- void func(num n)void func(char n)と区別したいですよね?)。

したがって、enum numcharではないため、operator<<(int)が使用され、基になる型がcharであっても、整数値が出力されます。完全に賢明というわけではありませんが、これが起こると確信しています。

5
Mats Petersson