私は列挙型で遊んでいて、いくつかの例を再現しようとしました これから ページ。最初の例は意図したとおりに機能しましたが、次のコードでいくつかの興味深い結果が得られました。
#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桁以外の値をどのように制御できるのかを知りたいです。
これは実際にはchar
オーバーロードに移動するはずです。残念ながら、問題のコンパイラはどれも実装していません DR 1601 。
基になる型が固定されているスコープ外の列挙型のprvalue([dcl.enum])は、基になる型のprvalueに変換できます。
これは、num
をchar
に昇格できることを意味します。
さらに、汎整数拡張をその基になる型に適用できる場合は、基になる型が固定されているスコープなし列挙型のprvalueを、プロモートされた基になる型のprvalueに変換することもできます。
したがって、num
をint
に昇格させることもできます。
関連する候補者は次のとおりです。
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
からchar
とnum
からint
の両方にプロモーションランクがあります。
DR1601より前では、これらは同等に優れているため、テンプレート/非テンプレートタイブレーカーが付属しています。最初のものは関数テンプレートです。 2番目の関数は単純なメンバー関数なので、2番目の関数が優先されます。
DR1601は、次のようなルールを追加しました。
基になる型が基になる型に固定されている列挙型をプロモートする変換は、2つが異なる場合、プロモートされる基になる型に昇格する変換よりも優れています。
これは、num
からchar
がnum
からint
よりも優れていることを意味します。したがって、最初のオーバーロードがより適切に一致するため、選択する必要があります。
C++標準(4.5インテグラルプロモーション)による
4基になる型が固定されている(7.2)スコープのない列挙型のprvalueは、基になる型のprvalueに変換できます。 さらに、汎整数拡張をその基になる型に適用できる場合、基になる型が固定されているスコープなし列挙型のprvalueも、昇格された基になる型のprvalueに変換できます。
したがって、汎整数拡張が適用され、int型のオブジェクトの演算子<<が呼び出されます。
enum num : char
と言うときは、num
がchar
に関して内部的に実装されているが、それでも自動的に整数値に変換できるという事実を表します。つまり、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
行をコンパイルエラーにするには、enum
をenum 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までの一般的な数値だけを表す列挙型は現実的なユースケースではないことに注意してください。
2つは2つの異なる演算子のオーバーロードを呼び出すため:
最初は非メンバーを呼び出しますoperator<<
ために std::ostream
およびchar
。文字を印刷します。
2番目の例では、メンバーをoperator<<
for int
は、他の回答で説明されているように、整数の昇格によるものです。
その理由は、enum : char
がchar
と同じではないためです(これはまさに私たちが望んでいることです-割り当て互換であっても、列挙型が他のタイプと同じになることは望ましくありません- void func(num n)
をvoid func(char n)
と区別したいですよね?)。
したがって、enum num
はchar
ではないため、operator<<(int)
が使用され、基になる型がchar
であっても、整数値が出力されます。完全に賢明というわけではありませんが、これが起こると確信しています。