名前空間
変種

暗黙の変換

提供: cppreference.com

異なる型の値が期待される文脈で式が使用されたとき、変換が発生することがあります。

int n = 1L; // 式 1L は long 型ですが、 int が期待されています。
n = 2.1; // 式 2.1 は double 型ですが、 int が期待されています。
char *p = malloc(10); // 式 malloc(10) は void* 型ですが、 char* が期待されています。

変換は以下の状況で行われます。

代入であるかのような変換

  • 代入演算子において、右側の被演算子の値は、左側の被演算子の修飾されていない型に変換されます。
  • スカラー初期化において、初期化子式の値は初期化中のオブジェクトの修飾されていない型に変換されます。
  • プロトタイプを持つ関数の関数呼び出し式において、各実引数式の値は対応する仮引数の修飾されていない宣言された型に変換されます。
  • return 文において、 return の引数は、その関数の戻り値の型のオブジェクトに変換されます。

実際の代入は、変換に加えて、浮動小数点型から余分の範囲および精度を除去することと、オーバーラップを禁止していることに注意してください。 これらの特性は代入であるかのような変換には適用されません。

デフォルト引数昇格

関数呼び出し式において、

1) プロトタイプのない関数の呼び出しの実引数について、または
2) 可変長引数関数で、省略記号引数にマッチする実引数について、

整数型の実引数はそれぞれ整数昇格 (後述) が行われ、 float 型の実引数はそれぞれ double 型に暗黙に変換されます。

int add_nums(int count, ...);
int sum = add_nums(2, 'c', true); // add_nums は3個の int (2, 99, 1) を用いて呼ばれます。

float complex および float imaginary はこの文脈では double complex および double imaginary に昇格されないことに注意してください。

通常の算術変換

以下の算術演算子の実引数は共通の実型 (計算が行われる型) を得る目的のために暗黙に変換されます。

1) 一方の被演算子が long double,、 long double complex、または long double imaginary の場合、他方の被演算子は以下のように暗黙に変換されます。
  • 整数型または実数浮動小数点型は long double になります。
  • 複素数型は long double complex になります。
  • 虚数型は long double imaginary になります。
2) そうでなく、一方の被演算子が doubledouble complex、または double imaginary の場合、他方の被演算子は以下のように暗黙に変換されます。
  • 整数型または実数浮動小数点型は double になります。
  • 複素数型は double complex になります。
  • 虚数型は double imaginary になります。
3) そうでなく、一方の被演算子が floatfloat complex、または float imaginary の場合、他方の被演算子は以下のように暗黙に変換されます。
  • 整数型は float になります (実数型で唯一有り得るのは float であり、これはそのままです)。
  • 複素数型は float complex になります。
  • 虚数型は float imaginary になります。
4) そうでなければ、両方の被演算子は整数です。 この場合、
まず最初に、両方の被演算子が整数昇格 (後述) されます。 その後、
  • 昇格後の型が同じ場合は、その型が共通型です。
  • そうでなく、昇格後の型が両方とも同じ符号有無 (両方とも符号付きまたは両方とも符号なし) の場合は、変換順位 (後述) が低い方の被演算子が、変換順位が高い方の被演算子の型に変換されます。
  • そうでなく (符号有無が異なる)、符号なしの方の被演算子が符号付きの方より変換順位

高い場合は、符号付きの方の被演算子が符号なしの方の型に暗黙に変換されます。

  • そうでなく (符号有無が異なり、符号付きの被演算子の順位が符号なしの被演算子の順位より高い)、符号付きの型が符号なしの型のすべての値を表現できる場合は、符号なしの方の被演算子が符号付きの方の型に暗黙に変換されます。
  • そうでなければ、両方の被演算子が、符号付きの方の型に対応する符号なし版の型に、暗黙に変換されます。
1.f + 20000001; // int が float に変換され、 20000000.00 となり、
                // 加算後、 float に丸められて、 20000000.00 になります。
(char)'a' + 1L; // まず、 char が int に昇格し戻されます。
                // これは符号付き+符号付きの順位が異なるケースです。
                // int が long に変換され、結果は符号付き long の 98 です。
2u - 10; // 符号付き+符号なしで同順位のケース。
         // 10 は符号なしに変換されます。 符号なしの算術は modulo UINT_MAX+1 であり、
         // 結果は unsigned int 型の UINT_MAX-7 (32ビットの場合は 4294967288) です。
0UL - 1LL; // 符号付き+符号なしで符号付きの方が順位が高いケース。
           // sizeof(long) == sizeof(long long) の場合、符号付きはすべての符号なしを表現できません。
           // これは最後のケースです。 両方の被演算子が unsigned long long に変換されます。
           // 結果は unsigned long long 型の 18446744073709551615 (ULLONG_MAX) です。

結果の型は以下のように決定されます。

  • 両方の被演算子が複素数の場合、結果の型は複素数です。
  • 両方の被演算子が虚数の場合、結果の型は虚数です。
  • 両方の被演算子が実数の場合、結果の型は実数です。
  • 2つの浮動小数点被演算子の型ドメインが異なる (複素数vs実数、複素数vs虚数、または虚数vs実数) 場合、結果の型は複素数です。
double complex z = 1 + 2*I;
double f = 3.0;
z + f; // z はそのままです。 f は double に変換されます。 結果は double complex です。

いつも通り、浮動小数点演算子の結果は、その型が表すよりも広い範囲、高い精度を持つことがあります (FLT_EVAL_METHOD を参照してください)。

ノート: 実数と虚数の被演算子は複素数に暗黙に変換されません。 無限大、 NaN、負のゼロが関わる特定のケースにおいて望ましくない結果を生成する上に、余計な計算が必要なためです。 例えば、もし実数が複素数に変換されるならば、 2.0×(3.0+i∞) は、正しい 6.0+i∞ ではなく、 (2.0+i0.0)×(3.0+i∞) ⇒ (2.0×3.0–0.0×∞) + i(2.0×∞+0.0×3.0) ⇒ NaN+i∞ と評価されるでしょう。 もし虚数が複素数に変換されるならば、 i2.0×(∞+i3.0) は –6.0 + i∞ ではなく (0.0+i2.0) × (∞+i3.0) ⇒ (0.0×∞ – 2.0×3.0) + i(0.0×3.0 + 2.0×∞) ⇒ NaN + i∞ と評価されるでしょう。

ノート: 通常の算術変換にかかわらず、計算は as-if ルールの下でこれらのルールによる指定子より狭い型で行われる可能性があります。

値変換

左辺値変換

あらゆる非配列型のあらゆる左辺値式は、以下のいずれか

以外の文脈で使用されたとき、左辺値変換が行われます。 型は同じままですが、 const/volatile/restrict 修飾子および atomic の性質 (もしあれば) は失われます。 値は同じままですが、左辺値の性質は失われます (アドレスを取れなくなります)。

その左辺値が不完全型の場合、動作は未定義です。

その左辺値がアドレスを決して取れない自動記憶域期間のオブジェクトを指示する場合、および、そのオブジェクトが初期化されていない (初期化子なしで宣言され、まだ代入されていない) 場合、動作は未定義です。

この変換はオブジェクトの値のその場所からのメモリロードをモデル化します。

volatile int n = 1;
int x = n;            // n に対する左辺値変換は n の値を読み込みます。
volatile int* p = &n; // 左辺値変換なし。 n の値を読み込みません。

配列からポインタへの変換

配列型のあらゆる左辺値式は、以下のいずれか

以外の文脈で使用されたとき、その最初の要素を指す非左辺値ポインタに変換されます。

その配列が register 宣言されている場合、動作は未定義です。

int a[3], b[3][4];
int* p = a;      // &a[0] に変換されます。
int (*q)[4] = b; // &b[0] に変換されます。

関数からポインタへの変換

あらゆる関数指示子式は、以下のいずれか

以外の文脈で使用されたとき、その式が指示する関数を指す非左辺値ポインタに変換されます。

int f(int);
int (*p)(int) = f; // &f に変換されます。
(***p)(1); // f の逆参照と &f への変換を繰り返しています。

暗黙の変換の意味論

暗黙の変換 (代入であるかのようなまたは通常の算術変換のいずれか) は以下の2つのステージから構成されます。

1) 値変換 (適用可能であれば)
2) 以下に挙げる変換のうちのひとつ (目的の型を生成できるならば)

互換な型

任意の型から任意の互換な型への変換は、常に無演算であり、その表現を変更しません。

uint8_t (*a)[10];         // uint8_t が unsigned char の typedef であるとします。
unsigned char (*b)[] = a; // その場合、これらのポインタ型は互換です。

整数昇格

整数昇格は、 int より低いまたは等しい順位を持つ整数型または _Bool、 int、 signed int、 unsigned int 型のビットフィールドから、 int または unsigned int 型への、暗黙の変換です。

元の型 (またはビットフィールド) の値の範囲全体を int で表現可能であれば、値は int 型に変換されます。 そうでなければ、値は unsigned int に変換されます。

整数昇格は、その値を、符号を含めて、維持します。

int main(void) {
   void f(); // 旧形式の関数宣言。
   char x = 'a'; // int から char への整数変換。
   f(x); // char から int への整数昇格。
}
void f(x) int x; {} // この関数は int を期待します。

上で述べた順位とは、すべての整数型が持つ性質であり、以下のように定義されます。

1) すべての符号付き整数型の順位は異なり、その精度と共に上昇します (つまり signed char < short < int < long < long long となります)。
2) 符号付き整数型の順位とその対応する符号なし整数型の順位は同じです。
3) 標準整数型の順位は同じサイズの拡張整数型の順位より高いです (つまり __int64 < long long < __int128 となります)。
4) char の順位は signed char の順位および unsigned char の順位と同じです。
5) _Bool の順位は他のあらゆる標準整数型の順位より低いです。
6) 列挙型の順位はそれと互換な整数型の順位と等しいです。
7) 順位は推移的です。 T1 < T2 かつ T2 < T3 ならば T1 < T3 です。
8) 上記でカバーされない拡張整数型の相対順位は処理系定義です。

ノート: 整数昇格は以下の場合にのみ適用されます。

  • 通常の算術変換 (上を参照) の一部として。
  • デフォルト引数昇格 (上を参照) の一部として。
  • 単項算術演算子 + および - の被演算子。
  • 単項ビット否定演算子 ~ の被演算子。
  • シフト演算子 << および >> の被演算子 (両側とも)。

ブーリアン変換

あらゆるスカラー型の値は _Bool に暗黙に変換できます。 ゼロと比較して等しい値は 0 に変換され、それ以外のすべての値は 1 に変換されます。

bool b1 = 0.5;              // b1 == 1 (もし int に変換したならば 0 になったでしょう)
bool b2 = 2.0*_Imaginary_I; // b2 == 1 (もし int に変換したならば 0 になったでしょう)
bool b3 = 0.0 + 3.0*I;      // b3 == 1 (もし int に変換したならば 0 になったでしょう)
bool b4 = 0.0/0.0;          // b4 == 1 (NaN は 0 と等しくありません)

整数変換

あらゆる整数型の値は任意の他の整数型に暗黙に変換できます。 上述の昇格およびブーリアン変換でカバーされる場合を除いて、ルールは以下の通りです。

  • 目的の型がその値を表現できる場合は、値は変更されません。
  • そうでなく、目的の型が符号なしの場合は、目的の型に収まるまで繰り返し 2b
    が加算または減算されます (b は目的の型のビット数です)。 別の言い方をすると、符号なし整数はモジュロ算術を実装します。
  • そうでなければ (目的の型が符号付きの場合)、動作は処理系定義です (シグナルを発しても構いません)。
char x = 'a'; // int → char。 結果は変更されません。
unsigned char n = -123456; // 目的の型は符号なし。 結果は 192 (つまり -123456+483*256) です。
signed char m = 123456;    // 目的の型は符号付き。 結果は処理系定義です。
assert(sizeof(int) > -1);  // assert は失敗します。
                           // 演算子 > により -1 が size_t に変換されます。
                           // 目的の型は符号なしであり、結果は SIZE_MAX です。

実数浮動小数点と整数の変換

あらゆる実数浮動小数点型の有限の値は、任意の整数型の値に暗黙に変換できます。 上述のブーリアン変換でカバーされる場合を除いて、ルールは以下の通りです。

  • 小数部は破棄されます (ゼロに向かって切り捨てられます)。
  • 結果の値が目的の型で表現できる場合は、その値が使用されます。
  • そうでなければ、動作は未定義です。
int n = 3.14; // n == 3
int x = 1e10; // 未定義動作 (32ビットの場合)

あらゆる整数型の値は任意の実数浮動小数点型に暗黙に変換できます。

  • 目的の型で正確に表現できる場合は、値は変更されません。
  • 表現できるけれども正確には表現できない場合は、結果はより大きな最も近い値またはより小さな最も近い値になります (別の言い方をすると、丸めの方向は処理系定義です)。 IEEE 算術がサポートされている場合は、丸めは最も近い値です。 この場合に FE_INEXACT が発生するかどうかは未規定です。
  • 表現できない場合は、動作は未定義です。 IEEE 算術がサポートされている場合は、 FE_INVALID が発生し、結果の値は未規定です。

この変換の結果は目的の型より広い範囲と高い精度を持つことがあります (FLT_EVAL_METHOD を参照してください)。

浮動小数点から整数への変換で FE_INEXACT の制御が必要な場合は、 rint および nearbyint が使用できます。

double d = 10; // d = 10.00
float f = 20000001; // f = 20000000.00 (FE_INEXACT)
float x = 1+(long long)FLT_MAX; // 未定義動作

実数浮動小数点の変換

あらゆる実数浮動小数点型の値は、任意の他の実数浮動小数点型に暗黙に変換できます。

  • 目的の型で正確に表現できる場合は、値は変更されません。
  • 表現できるけれども正確には表現できない場合は、結果はより大きな最も近い値またはより小さな最も近い値になります (別の言い方をすると、丸めの方向は処理系定義です)。 IEEE 算術がサポートされている場合は、丸めは最も近い値です。
  • 表現できない場合は、動作は未定義です。

この変換の結果は目的の型より広い範囲と高い精度を持つことがあります (FLT_EVAL_METHOD を参照してください)。

double d = 0.1; // d = 0.1000000000000000055511151231257827021181583404541015625
float f = d;    // f = 0.100000001490116119384765625
float x = 2*(double)FLT_MAX; // 未定義

複素数型の変換

あらゆる複素数型は任意の他の複素数型に暗黙に変換できます。 実部と虚部は個別に実数浮動小数点型の変換ルールに従います。

double complex d = 0.1 + 0.1*I;
float complex f = d; // f は (0.100000001490116119384765625, 0.100000001490116119384765625) です。

虚数型の変換

あらゆる虚数型の値は任意の他の虚数型に暗黙に変換できます。 虚部は実数浮動小数点型の変換ルールに従います。

double imaginary d = 0.1*_Imaginary_I;
float imaginary f = d; // f は 0.100000001490116119384765625*I です。

実数と複素数の変換

あらゆる実数浮動小数点型の値は任意の複素数型に暗黙に変換できます。

  • 結果の実部は実数浮動小数点型の変換ルールによって決定されます。
  • 結果の虚部は正のゼロ (ゼロに正負の区別がない処理系ではただのゼロ) です。

あらゆる複素数型の値は任意の実数浮動小数点型に暗黙に変換できます。

  • 実部は実数浮動小数点型のルールに従って変換されます。
  • 虚部は破棄されます。

ノート: 複素数から実数への変換では、虚部の NaN は実数の結果に伝播されません。

double complex z = 0.5 + 3*I;
float f = z;  // 虚部は破棄されます。 f は 0.5 になります。
z = f;        // z ば 0.5 + 0*I になります。

実数と虚数の変換

あらゆる虚数型の値は任意の実数型 (整数または浮動小数点) に暗黙に変換できます。 結果は常に正のゼロです。 ただし、目的の型が _Bool のときは、ブーリアン変換のルールが適用されます。

あらゆる実数型の値は任意の虚数型に暗黙に変換できます。 結果は常に正の虚数のゼロです。

double imaginary z = 3*I;
bool b = z;  // ブーリアン変換。 B は true になります。
float f = z; // 実数と虚数の変換。 f は 0.0 になります。
z = 3.14;    // 実数と虚数の変換。 z は 0*_Imaginary_I になります。

複素数と虚数の変換

あらゆる虚数型の値は任意の複素数型に暗黙に変換できます。

  • 結果の実部は正のゼロです。
  • 結果の虚部は対応する実数型の変換ルールに従います。

あらゆる複素数型の値は任意の虚数型に暗黙に変換できます。

  • 実部は破棄されます。
  • 結果の虚部は対応する実数型の変換ルールに従います。
double imaginary z = I * (3*I); // 複素数の結果 -3.0+0i の実部は失われます。
                                // z は 0*_Imaginary_I になります。

ポインタ変換

void へのポインタはオブジェクト型への任意のポインタとお互い暗黙に変換できます。 これは以下の意味論を持ちます。

  • オブジェクトへのポインタを void へのポインタに変換し、再び元の型に戻した場合、その値は元のポインタと比較すると等しいです。
  • それ以外のいかなる保証も提供されません。
int* p = malloc(10 * sizeof(int)); // malloc は void* を返します。

修飾されていない型へのポインタはその型の修飾されたバージョンに暗黙に変換できます (別の言い方をすると、 constvolatile、および restrict 修飾子を追加できます)。 元のポインタと結果のポインタを比較すると等しいです。

int n;
const int* p = &n; // &n は int* 型です。

0 の値を持つあらゆる整数定数式および void* 型にキャストしたゼロの値を持つ整数ポインタ式は任意のポインタ型 (オブジェクトへのポインタと関数へのポインタ両方) に暗黙に変換できます。 結果はその型のヌルポインタであり、その型のあらゆる非ヌルなポインタと比較して等しくないです。 この整数式または void* 式はヌルポインタ定数と言い、標準ライブラリはマクロ NULL としてこの定数の定義を提供しています。

int* p = 0;
double* q = NULL;

ノート

算術演算における符号付き整数のオーバーフローは未定義動作ですが、整数変換における符号付き整数型のオーバーフローは未規定なだけです。

一方、算術演算における (整数変換においても) 符号なし整数のオーバーフローは well-defined であり、モジュロ算術のルールに従いますが、浮動小数点から整数への変換における符号なし整数のオーバーフローは未定義動作です。 符号なし整数に変換できる実数浮動小数点型の値は開区間 (-1; Unnn_MAX+1) の値です。

unsigned int n = -1.0; // 未定義動作

ポインタと整数の間の変換 (ポインタから _Bool への変換およびゼロの値を持つ整数定数式からポインタへの変換は除きます)、オブジェクトへのポインタ間の変換 (いずれかが void へのポインタの場合は除きます)、関数へのポインタ間の変換 (その関数が互換な型の場合は除きます) は暗黙には行われず、キャスト演算子が必要です。

関数へのポインタとオブジェクトへのポインタ (void* も含みます) または整数との間の変換は (暗黙にも明示的にも) ありません。

参考文献

  • C11 standard (ISO/IEC 9899:2011):
  • 6.3 Conversions (p: 50-56)
  • C99 standard (ISO/IEC 9899:1999):
  • 6.3 Conversions (p: 42-48)
  • C89/C90 standard (ISO/IEC 9899:1990):
  • 3.2 Conversions

関連項目

暗黙の変換C++リファレンス