5. UNICODE
著者:梅谷 武
各言語におけるUNICODEの扱いについて調べる。
作成:2024-03-21
更新:2024-10-24

C++

 C++20でstd::u8string、char8_tが導入されたが、これによりUNICODE処理ができるようになったわけではない。今回の実験ではC++23のstd:printでstd::u8stringが表示できないという問題があり、それに対処するためにUTF-8文字列を格納する標準コンテナとしてstd::stringを用いている。
 C/C++言語でUNICODE処理を行なう場合、以前はOSに依存する方法が一般的であった。UNIX上では長くEUCが使われてきており、UNICODEを使う環境は整備されていなかった。現在ではUbuntu上C/C++でUNICODE処理を行なう標準的方法としてICU(International Components for Unicode)がある。
 ICUのC/C++言語インターフェースについてはICU 74.1を参照のこと。Ubuntuの最新のC/C++環境ではデフォルトでインストールされるようであるが、もしインストールされていなければ次のようにする。
$ sudo apt update
$ sudo apt upgrade
$ sudo apt install libicu-dev
 OCamlの基本データ型についてはオンラインマニュアル:Basic Data Types and Pattern Matchingを参照のこと。
 文字についての概説はThe Racket Guide>3 Built-in Datatypes>3.3 Characters、詳細仕様はThe Racket Reference>4 Datatypes>4.6 Charactersにある。
 文字列についての概説はThe Racket Guide>3 Built-in Datatypes>3.4 Strings (Unicode)、詳細仕様はThe Racket Reference>4 Datatypes>4.4 Stringsにある。
 文字列処理ライブラリSRFI 13: String Librariesを使っている。
 UTF-8で符号化された次の四つの文:
  1. How are you?
  2. Ça va?
  3. Πῶς πάς;
  4. お元気ですか?
について、空白を入れてこの順番に連結して一つの文字列にして表示し、その長さを求める。次にこの連結文字列に"お元気ですか?"が含まれるかどうかを検索し、最初に見つかった位置(先頭から何文字目か)を求める。

C++

 まずstd::stringを使って解いてみる。
 string.cpp:
#include <array>
#include <print>
#include <stdexcept>
#include <string>
 
void problem() {
  std::array<std::string, 4> strings = {
      "How are you?",
      "Ça va?",
      "Πῶς πάς;",
      "お元気ですか?"};
  std::string concat;
  for (std::size_t i = 0; i < strings.size(); ++i) {
    concat += strings[i];
    if (i < strings.size() - 1)
      concat += " ";
  }
  std::println("{}", concat);
  std::println("length = {}", concat.length());
  std::string key = strings[3];
  auto pos = concat.find(key);
  if (pos != std::string::npos) {
    std::println("{} found at position {}", key, pos);
  } else {
    std::println("{} not found.\n", key);
  }
}
 
int main() {
  try {
    problem();
  } catch (const std::logic_error &e) {
    std::println(stderr, "Logic error: {}", e.what());
  } catch (const std::runtime_error &e) {
    std::println(stderr, "Runtime error: {}", e.what());
  } catch (const std::exception &e) {
    std::println(stderr, "Another std::exception: {}", e.what());
  } catch (...) {
    std::println(stderr, "Unknown exception.");
  }
  return 0;
}


 makefile:
CPPG = g++
CPPL = clang++
CPPFLAGS = -std=c++23 -O2
SRC = string.cpp
EXES = string_g string_l
 
all: $(EXES)
 
string_g: $(SRC)
	$(CPPG) $(CPPFLAGS) -o $@ $(SRC)
 
string_l: $(SRC)
	$(CPPL) $(CPPFLAGS) -o $@ $(SRC)
 
clean:
	rm -f $(EXES)


$ measure ./string_g
How are you? Ça va? Πῶς πάς; お元気ですか?
length = 56
お元気ですか? found at position 37
======================================
Process exited with status: 0
total time:  0.002121 [sec]
mem  size:       3672 [KB]
code size:        119 [KB]
$ measure ./string_l
How are you? Ça va? Πῶς πάς; お元気ですか?
length = 56
お元気ですか? found at position 37
======================================
Process exited with status: 0
total time:  0.001323 [sec]
mem  size:       3772 [KB]
code size:         96 [KB]
 文字列の基本操作はStringモジュール、照合はStrモジュールを用いる。
 string.ml:
open Printf
 
let problem =
  let strings = [
    "How are you?";
    "Ça va?";
    "Πῶς πάς;";
    "お元気ですか?"] in
  let concat = String.concat " " strings in
  printf "%s\n" concat;
  printf "length = %d\n" (String.length concat);
  let key = List.nth strings 3 in
  let pos =
    try Some (Str.search_forward (Str.regexp_string key) concat 0)
    with Not_found -> None
  in match pos with
    | Some index -> printf "%s found at position %d\n" key index
    | None -> printf "%s not found.\n" key
 
let () =
  try
    problem
  with
    | Invalid_argument msg ->
        eprintf "Logic error: %s\n" msg
    | Failure msg ->
        eprintf "Runtime error: %s\n" msg
    | exn ->
        eprintf "Another exception: %s\n" (Printexc.to_string exn)


 Strモジュールをリンクするにはパス指定が必要である。
 makefile:
OCAMLOPT = ocamlopt
OCAMLFLAGS = -O2
LIBPATH = -I +str
LIBS = str.cmxa
SRC = string.ml
EXES = string
 
all: $(EXES)
 
string: $(SRC)
	$(OCAMLOPT) $(OCAMLFLAGS) $(LIBPATH) -o $@ $(LIBS) $(SRC)
 
clean:
	rm -f $(EXES) *.o *.cmx *.cmi


$ measure ./string
How are you? Ça va? Πῶς πάς; お元気ですか?
length = 56
お元気ですか? found at position 37
======================================
Process exited with status: 0
total time:  0.000000 [sec]
mem  size:       3176 [KB]
code size:       2668 [KB]
 文字列照合にはsrfi/13モジュールを用いる。
 string.rkt:
#lang racket
(require iso-printf)
(require srfi/13)
 
(define (problem)
  (define strings '("How are you?" "Ça va?" "Πῶς πάς;" "お元気ですか?"))
  (define concat (string-join strings " "))
  (printf "%s\n" concat)
  (printf "length = %d\n" (string-length concat))
  (define key (list-ref strings 3))
  (define pos (string-contains concat key))
  (if (integer? pos)
    (printf "%s found at %d\n" key pos)
    (printf "%s not found.\n" key)))
 
(define (main)
  (with-handlers
    ([exn:fail? (λ (e) (eprintf "%s\n" (exn-message e)))]
     [exn? (λ (e) (eprintf "Unexpected: %s\n" (exn-message e)))])
  (problem)))
 
(main)


$ raco exe string.rkt
$ measure ./string
How are you? Ça va? Πῶς πάς; お元気ですか?
length = 36
お元気ですか? found at 29
======================================
Process exited with status: 0
total time:  0.491579 [sec]
mem  size:     135316 [KB]
code size:      12740 [KB]
 UNICODEを意識することなく単なる文字列処理としてプログラミングできる。非英語圏プログラマーの立場からするとこの結果が望ましい。
 言語仕様として標準的な文字列処理がUNICODEに対応しているのは唯一Racketのみである。C/C++ではICUライブラリを導入することによりUNICODE処理は可能になるが、言語としての一貫性は損なわれている。
 ここではC++でICUライブラリを使って上の課題を解いてみる。

C++

 icu::UnicodeStringは文字列をUTF-16形式で格納しており、これを直接表示することはできない。これを表示するにはUTF-8形式に変換してstd::stringに格納し、それを表示する。ICUライブラリにはこの機能が見当たらないためu2sという関数を作る。
 ustring.cpp:
#include <array>
#include <print>
#include <stdexcept>
#include <string>
#include <unicode/unistr.h> 
 
std::string u2s(const icu::UnicodeString &ustr) {
  std::string str;
  ustr.toUTF8String(str);
  return str;
}
 
void problem() {
  std::array<icu::UnicodeString, 4> strings = {
      icu::UnicodeString::fromUTF8("How are you?"),
      icu::UnicodeString::fromUTF8("Ça va?"),
      icu::UnicodeString::fromUTF8("Πῶς πάς;"),
      icu::UnicodeString::fromUTF8("お元気ですか?")};
  icu::UnicodeString concat;
  for (std::size_t i = 0; i < strings.size(); ++i) {
    concat += strings[i];
    if (i < strings.size() - 1)
      concat += " ";
  }
  std::println("{}", u2s(concat));
  std::println("length = {}", concat.length());
  icu::UnicodeString key = strings[3];
  int32_t pos = concat.indexOf(key);
  if (pos != -1) {
    std::println("{} found at position {}", u2s(key), pos);
  } else {
    std::println("{} not found.", u2s(key));
  }
}
 
int main() {
  try {
    problem();
  } catch (const std::logic_error &e) {
    std::println(stderr, "Logic error: {}", e.what());
  } catch (const std::runtime_error &e) {
    std::println(stderr, "Runtime error: {}", e.what());
  } catch (const std::exception &e) {
    std::println(stderr, "Another std::exception: {}", e.what());
  } catch (...) {
    std::println(stderr, "Unknown exception.");
  }
  return 0;
}


 コンパイル時にICUライブラリをリンクする。
 makefile:
CPPG = g++
CPPL = clang++
CPPFLAGS = -std=c++23 -O2
LIBS = -licuuc
SRC = ustring.cpp
EXES = ustring_g ustring_l
 
all: $(EXES)
 
ustring_g: $(SRC)
	$(CPPG) $(CPPFLAGS) -o $@ $(SRC) $(LIBS)
 
ustring_l: $(SRC)
	$(CPPL) $(CPPFLAGS) -o $@ $(SRC) $(LIBS)
 
clean:
	rm -f $(EXES)


$ measure ./ustring_g
How are you? Ça va? Πῶς πάς; お元気ですか?
length = 36
お元気ですか? found at position 29
======================================
Process exited with status: 0
total time:  0.003420 [sec]
mem  size:       4836 [KB]
code size:        133 [KB]
$ measure ./ustring_l
How are you? Ça va? Πῶς πάς; お元気ですか?
length = 36
お元気ですか? found at position 29
======================================
Process exited with status: 0
total time:  0.004494 [sec]
mem  size:       4656 [KB]
code size:        102 [KB]
This document is licensed under the MIT License.
Copyright (C) 2024 Takeshi Umetani