今回はソースコードから見ていくことにしましょう。

やろうとしていることは簡単です。標準入力から1文字読み込み、アルファベットの大文字であれば小文字に変換して標準出力に書き込んでいます。

ここで、アルファベットの大文字かどうかの判別にはisupperを使うべきだと主張する方がおられるかもしれませんが、その主張は必ずしも正しくありません。なぜならisupperの挙動はその時点で設定されているロケールに依存するからです。確実に’A’~’Z’の範囲に収まっているかどうかを判定するには、やはり自前で処理を書かなければなりません。

というわけで、isupperではなく自前で判定を行っていること自体は間違いではないわけですが、問題はその判定方法にあります。ASCIIやASCIIと(ほぼ)互換性のある文字コードの場合、多バイト文字さえ考慮しなければ上のコードでも問題ありません。しかし、このコードには少なくとも移植性がありません。C言語やC++の規格では、アルファベットの文字コードが連続していることが保証されていないからです。

アルファベットの文字コードが連続していない典型的な実例はEBCDICです。メインフレーム等で使用される文字コードですが、文字コードの配置を見ると、’A’~’I’, ‘J’~’R’, ‘S’~’Z’の3つに分かれてしまっています。これでは、’A’ <= ch && ch <= ‘Z’ の条件式ではアルファベットの大文字以外に対しても真になってしまいます。

なお、本来のEBCDICではアルファベットの小文字を扱うことができませんが、通常は小文字を扱えるように拡張が行われています。アルファベットの小文字は基本実行文字集合ですので、規格合致処理系では使えることを保証しなければなりません。幸いにしてEBCDICの小文字を扱うための拡張では、大文字の文字コードから0x40を引けば小文字になります。しかし、大文字から一定の値を引けば対応する小文字になるかどうかも、規格上は保証されていないのです。

一方で、数字、すなわち’0’~’9’の文字コードが連続していることは規格で保証されています。そのため、アルファベットに比べれば、数字はずっと扱いやすいのです。

現実の問題として、ASCII系の文字コードを用いる処理系と、EBCDIC等の非ASCII系の文字コードを用いる処理系の間で移植性が要求されるケースは非常に稀です。ですから、アルファベットの連続性が必ずしも保証されないことを理解しておくことは重要ですが、実際のコーディングでは、対象とする処理系がASCII系の文字コードであると仮定できるのであれば、その旨を明記した上で、アルファベットの連続性を期待しても差し支えないでしょう。

また、C++20以降であれば、 char8_t型、char16_t型、char32_t型がそれぞれUTF-8、UTF-16、UTF-32であることが規定されていますので、たとえばu’A’~u’Z’が連続していると考えて問題ありません。