38
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

整数型に 'c' が代入できるそれぞれの事情

Last updated at Posted at 2022-09-03

これは何?

という記事で

c
int a;
a = 'c';

が問題なく動いている話や 素晴らしいコメント があって面白かったので、記事にしてみた。

それぞれの事情

C言語

C 言語の場合、そもそも 'c' の型が int である。
なので

c
int a;
a = 'c';

は、わりと面白くない。

Twitter 方面で、 'c' の型が int であることに疑問を抱いている方が散見されるので追記。

なぜそうなっているのかは知らないんだけど、C 言語では古来よりシングルクオートで囲まれたリテラルは int 型になっている。規格書にもそう書いてある。

古い C でも

c
printf( "sizeof('c') is %d\n", (int)sizeof('c'));

のようにすると「sizeof('c') is 1」ではなく「sizeof('c') is 4」など(4 じゃないかも)となることから、 'c'char ではないことがわかる。'c'char だと思いこんでいる方は是非試してほしい。なお、C++ では 'c'char なので、C++ としてコンパイルしないように注意が必要。

それはともかく。実際問題、'c'char ではなく int だからといって sizeof に入れる以外の方法で違いがわかるパターンがないので、知っていても実用上嬉しいことは特に無い感じだった。C11 が出るまでは。
C11 で _Generics という新機能が入り、'c' が int だと知らないと引っかかる罠が発生した ので、注意が必要。

C++ / C# / D / Java / Scala

C++ の場合、 'c'char となっている。なので

c++
int a;
a = 'c';

とした場合、char から int への暗黙の変換が発生する。この変換はほぼ必ず(sizeof(int)==sizeof(char) で、なおかつ charunsigned の場合は安全ではないが、そんな処理系に触る機会がある人は稀だろう)安全なので、警告は出ない。

C# と D のことはよく知らないんだけど、同じように char のようなものから int のようなものへの暗黙の変換が起きるんじゃないかな(調べてない)。Java も同様かな(よく知らない)。

Scala の場合「弱い適合性」というルールに従って Char から Int への変換が許容されるらしい。わかってないけど。

Go

Go は事情が違う。

go
var a int
a = 'c'

とした場合。a = 'c' の右辺にある 'c' は untyped rune。型は無いんだけど、強いて言えば rune みたいなことだと思う。
左辺の aint。型なしから int への変換は、右辺の値が int の範囲内の値であれば成功する。

以下のように

go
r := 'c'
var a int = r // cannot use r (type rune) as type int in assignment

untyped rune である 'c' を型指定なしで変数に受けると、受けた変数は暗黙のうちに rune になる。rune から int への暗黙の変換はできないのでエラーになる。

Groovy

Groovy の事情も面白い。

groovy
Integer a
a = 'c'
printf("[%c]\n", a)

a = 'c' で起こっていることはたぶん(自信ない)

  1. 'c' は文字列
  2. 文字列を整数に代入しようとするので、暗黙の変換を試みる
  3. 文字列の長さが 1文字の場合に限って、先頭の文字の文字コードを整数型の値として取得できる

ということだと思う。

groovy
Integer a
s = 'c'
a = s
print("a=${a}/${a.class}  s=${s}/${s.class}") // a=99/class java.lang.Integer  s=c/class java.lang.String

のように、文字列から整数への変換ができる。また、 s の値を '''hoge' などにするとエラーになる。

それと。
上記の例では a の型が確定するような書き方なので a = 'c' などとすると 'c' を整数に変換する必要が出てくるが、a の型を宣言しないと

groovy
a = 50
printf("${a} ${a.class}\n") // 50 class java.lang.Integer
a = 'c'
printf("${a} ${a.class}\n") // c class java.lang.String

このように、 a の型が変わる。

zig

zig の事情は go と似ている。

zig
const stdout = @import("std").io.getStdOut().writer();

pub fn main() !void {
    var a: c_int = 'c';
    try stdout.print("a={}\n", .{a}); // a=99
}

'c' の型は comptime_int で、コンパイル時に値が確定しているけどビット長が確定していない整数型。
これを c_int に代入すると、値の範囲を検査して OK なら代入できる。
と、ここまでは go と同じ感じ。

go との違いは、情報欠落がない場合は暗黙の変換ができるという点。

zig
const stdout = @import("std").io.getStdOut().writer();

pub fn main() !void {
    var s: u8 = 'c';
    var a: c_int = s;
    try stdout.print("a={}\n", .{a}); // a=99
}

コンパイル時に値がわかっていれば

zig
const stdout = @import("std").io.getStdOut().writer();

pub fn main() !void {
    const s: i8 = 'c';
    var a: u32 = s; // 符号付きから符号なしへの変換
    try stdout.print("a={}\n", .{a}); // a=99
}

情報の欠落がありそうな変換でも暗黙の変換が普通にできる点は珍しいと思う。

さらに。 comptime_intu8 などの値を代入することもできるので

zig
const stdout = @import("std").io.getStdOut().writer();

pub fn main() !void {
    comptime var a: comptime_int = 1;
    comptime var z: u8 = 'z';
    try stdout.print("{}\n", .{a}); //=> 1
    a = 'c'; // 両辺 comptime_int
    try stdout.print("{}\n", .{a}); //=> 99
    a = z; // 左辺は comptime_int、右辺は u8
    try stdout.print("{}\n", .{a}); //=> 122
}

こういうパターンもある。この辺りも go の「型なし」とは大きく異る。

Julia

ほぼ使ったことがないのでよくわからない。仕様書も見たけどよくわからなかったので試したら。

julia1.6
a16::Int16 = 0
a32::Int32 = 0
a16 = 'c' # 'c' は 16bit の範囲内なので OK
a16 = '😀' # '😀' は U+1F600 なので 16bit の範囲外。エラー。
a32 = '😀' # U+1F600 も 32bit なら範囲内なので OK

どうも

  • 'c' は実質的に整数型。
  • a = 'c' のような代入があると、a'c' を表現できるかどうかをチェックし、表現できれば代入可能。

っぽい。

まとめ

整数型の変数 a に対して a = 'c' が合法である理由には各言語それぞれの事情がある。

上記をまとめると、推測も混じっているけどこんな感じ。

言語 事情
C 'c'int なので、両辺 int
C++ / C# / D / Java / Scala 'c'int ではないが、整数型。暗黙の変換で代入成立。
Go 'c' は untyped rune。異なる型への暗黙の変換はほぼ全部違法だが、untyped からの変換は、受け側の型が受け入れられる値ならOK。
Groovy 'c' は文字列。一文字の文字列の場合のみ、整数への変換が可能。
Zig 'c' は、comptime_intcomptime_int に限らず、コンパイル時に値がわかっている場合、受け側の型が受け入れられる値ならOK。
Julia 'c' はおそらく実質的に整数。代入先の整数型で表現できる場合には代入可能。たぶん。
38
14
14

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?