日本ソフトウェア科学会第 38 回大会 (2021 年度) 講演論文集

情報可視化やインタラクションのためのライブラリ

Siv3D の機能強化と C++17, C++20 への対応
鈴木 遼

上田 和紀

坂井 滋和

情報可視化やインタラクションのための C++ライブラリ Siv3D は 2016 年に OSS 化されて以降，現在までに5,000 回（コミット）を超える機能追加と改良が公開リポジトリ上で行われている．これらには近年のハードウェ
ア・開発ツール・言語仕様の進化，ユーザの利用事例の検証等を踏まえた，様々な新機能や API 設計の見直し，
C++17/C++20 への対応などの興味深い事例が含まれている．本稿では Siv3D の OSS 化以降 4 年間にわたる設計の変遷と取捨選択，および普及のための取り組みを紹介し，情報可視化やインタラクションに関するライブラリおよび現代的な C++ライブラリの設計と運用に役立つ知見を共有する．

Siv3D はゲームやメディアアートのようなイン

1 はじめにタ ラ ク ティブ な ア プ リ ケ ー ション を ，C++で 効

1. 1 Siv3D の概要

率 的 に コ ー ディン グ す る こ と を 目 的 に 開 発 さ れ

Siv3D はグラフィカルでインタラクティブなアプて い る ．2021 年 8 月 時 点 で の 直 近 1 年 間 の ダ ウ

リケーションの開発に便利な Application Program-ン ロ ー ド 数 は 約 1 万 回 で あ る ．動 作 環 境 は Win-ming Interface (API) を提供する C++ライブラリでdows, macOS, Linux, Web で ，コ ー ド リ ポ ジ ト

ある．Siv3D を導入することで，ユーザは次のよう

リ https://github.com/Siv3D/OpenSiv3D においな操作を組み合わせたアプリケーションプログラムをて MIT ライセンスで配布されている．約 2,000 ファ

非常に短いコードで記述できる [18]．

イル（コメントや空行を除き 205 KLOC）のオリジ• 図形や画像，テキスト，動画，3D モデルなど，
グラフィックスの描画

ナルのソースコードと，さらに 90 のサードパーティ・
オープンソースソフトウェア (OSS) から構成される．

• マウスやキーボード，Web カメラ，マイク，ゲー

Siv3D のアプリケーションコードのほとんどが，ムパッドなど，ヒューマンインタフェースデバイ

Siv3D により提供される型や関数を使って記述されス (HID) の使用るため，一般的な C++ライブラリよりも domain-• ウィンドウ処理，ファイルシステム，ネットワーク

speciﬁc language (DSL) としての性格が強い．テキス• 画像処理や音声処理

トや画像，図形をインタラクティブに表示する Siv3D• 物理演算や経路探索，幾何などの計算

アプリケーションのサンプルコードを次に示す．

• データ構造やアルゴリズム

1#include <Siv3D.hpp>

2

Enhancements to Siv3D, a Library for Information
Visualization and Interaction, Including Adopting
C++17/20
Ryo Suzuki, Kazunori Ueda, Shigekazu Sakai, 早稲田
大学, Waseda University.
This is an unrefereed paper. Copyrights belong to the
Author(s).

3void Main()

4

{

5
6
7
8
9
10

// 背景を水色に
Scene::SetBackground(ColorF{0.8,0.9,1.0});// フォントを作成
const Font font{ 60 };// 画像ファイルからテクスチャを作成
const Texture texture{ U"image.png" };// テクスチャを描画する座標
Vec2 pos{ 200,200 };

11
12
13
14

while (System::Update())

15

{

16
17

20
21

高く，間違えにくく，学習しやすいユーザコードを実// 画面の中心にテキストを表示
font(U"Hello, Siv3D!).drawAt(
Scene::Center(), Palette::Black);

5,000 回以上のコードの追加や変更（コミット）が公

22// マウスカーソルを中心に// 半透明の赤い円を描く
Circle{ Cursor::Pos(), 40 }.draw(ColorF{ 1, 0, 0, 0.5 });

23
24
25
26

現している．また，2016 年のオープンソース化以降，
開リポジトリ上で行われ，20 人以上がコミッタとしてライブラリの開発に参加するなど，活発な開発とユーザコミュニティ運営が続いている．

Siv3D の API 設計技法や，開発生産性の向上，ユー
ザコミュニティの継続に関する経験は，新しい C++

27// キーボードの[A] が押されたらif (KeyA.down()){// メッセージを出力
Print << U"Hello!";
}

28
29
30
31
32
33
34

// 画面上に"Move" と書かれた// ボタンを表示し, それが押されたらif (SimpleGUI::Button(U"Move",{640,40})){// 画面上のランダムな位置に座標を移動
pos = RandomVec2(Scene::Rect());
}

35
36
37
38
39
40
41
42

}

43
44

Siv3D は最新の C++仕様を積極的に活用しつつ，いくつかのテクニカルな API 設計により，生産性が// テクスチャを描く
texture.draw(pos);

18
19

1. 2 Siv3D の特徴

ライブラリの開発者のための再利用可能な知見として共有できると考える．

1. 3 関連記事
C++のライブラリや API の設計について，Reddy
が基本的な作法を広く論じている [11]．Gregory は，

C++によるゲームエンジンの構成を解説するなかで
C++ API 設計についても述べている [7]．Nystrom
は C++によるゲームプログラミングに有益なデザイ
ンパターンを複数取り上げ，現代的なコードによる}実装と用例を紹介している [9]．よりプリミティブでプログラムのエントリポイント main()（Windows

一般的な C++のコーディングルールとしては C++

では WinMain()）はライブラリ側にあり，ウィンドウCore Guidelines [16] が充実している．

やレンダリングエンジンなど必要なサブシステムの本稿ではこれらの記事で扱っている汎用的なルー初期化が終わった段階で，ライブラリ側がユーザの実

ルや技法については繰り返さず，代わりに Siv3D の装した Main()関数を呼び出す形でアプリケーション

API 設計で特定の課題を突破するために実装した技が実行される．Main()関数が制御を返すと，サブシス

巧的なコードや，最新の C++仕様の用例のうち，他

テムのリソースの解放など後片付けの処理が行われ，のプロジェクトでも導入しやすい事例を紹介する．

プログラムが終了する．ユーザがサブシステムのための定型的なコードを記述せずに済むこのような構成

1. 4 本稿の構成は，openFrameworks や Cinder など，C++のアプ

本稿ではまず，Siv3D で実際に使われている API

リケーション開発フレームワークでよく用いられる．

やコーディングスタイルをピックアップし，使い勝// 説明のための簡易的なコード
2 // （ライブラリ側）
3 int main() // エントリポイント
4 {5
SubSystems s;
6
if (!s.startUp())
7
return -1;
8Main(); // ユーザのプログラムを実行
9s.shutDown();
10 }手の良い C++ライブラリの実現のために C++から

1「もっと引き出す」応用的な設計技法について論じる．とくに，Siv3D のオープンソース化当初の論文[20] 以降に改良が進んだ部分に比重を置く．二番目に，Siv3D のオープンソースプロジェクトの継続とユー
ザコミュニティの発展のための取り組みについて述べる．三番目に，Siv3D のような規模の開発をどのようなツールの助けによって実現したかを紹介する．最後に，これからの Siv3D で解決したい課題と，C++// 一辺が50 の正方形
Rect rect{ 40, 20, 50 };

34
35
36

}

や開発ツールが将来どのように進化・整備されると，長方形の定義においては，中心座標を基準とするほSiv3D のようなライブラリの開発者にとって嬉しいかという意見を報告する．

うが都合が良いケースもあるし，左下や右下を基準にするほうが便利な場合もある．しかし，C++のオー

2 C++ライブラリ設計

バーロードの解決は引数の型と個数の対応関係によっ

本節では，Siv3D の使い勝手を改善するために取りて行われるため，左上座標を基準とするコンストラク

組んだ API 設計の工夫，C++17/C++20 への対応

タと中心座標を基準とするコンストラクタは単純にはなど，ライブラリ設計の参考となる事例を紹介する．

共存できない．このようなとき，オフセットの計算をラップした静的関数を提供するのが 1 つの解決方法

2. 1 柔軟な図形定義のためのコンストラクタオー
バーロード
図形を扱う C++ ライブラリは，通常，図形をい
くつかのクラスに分類し，それぞれのクラスのコンストラクタについて関数オーバーロード (function

であるが，コンストラクタではないため，new 式や，
一時オブジェクトの作成を介さずコンテナに要素を追加する emplacement が使えないなど，一貫性に欠く．
1struct Rect

2{

overloading) によって，目的の図形を最小限の入力か

4

ら柔軟に定義できる方法を提供する．ここでは長方形

6のクラス Rect を考える．

7

1
2

5

8

// ...
};

struct Point

9

{

10

int main()11{

int x, y;

3

};

12

6

struct Rect

14

7

{

15

4

// 中心座標と幅，高さからRect を作成
static Rect FromCenter(
Point center, int w, int h);

3

5

8

int x, y, w, h;

16// 左上を (0,0)におく正方形の定義に便利
explicit Rect(int size);18// 左上を (0,0)におく長方形の定義に便利
Rect(int w, int h);

21

9
10
11
12
13
14
15
17
18
19
20

23

// emplacement で構築できない
rects.push_back(Rect::FromCenter(pos, 100, 50));

20
22
23

}
コンストラクタのオーバーロードの文脈でこの問// Point 型の値から構築
Rect(Point pos, int size);

ために，タグとして機能するだけの，メンバを持たな// Point 型の値から構築
Rect(Point pos, int w, int h);26

rects.emplace_back(pos, 100, 50);19// 長方形の定義に便利
Rect(int x, int y, int w, int h);24
25

rects.emplace_back(pos, 100);

題を解決する方法として，タグ・ディスパッチング

21
22

std::vector<Rect> rects;

17// 正方形の定義に便利
Rect(int x, int y, int size);

16

Point pos{ 10, 20 };13(tag dispatching) がある．オーバーロードを増やすいクラスを登場させる．
1
2

struct TopLeft{};struct Center{};27

3// ...
29 };4struct Rect

5

{

30

6

28

31

int main()

7

32

{

8

33

// 左上の座標が (40,20)

9Rect(TopLeft, Point topLeft,int w, int h);Rect(Center, Point center, int w, int h);

10

できる．

// ...
12 };1struct TopLeft : Point

13

2

{

11

// コンストラクタの継承
using Point::Point;
5 };

14

int main()

3

15

{

4

Point pos{ 10, 20 };Rect a{ TopLeft{}, pos, 30, 40 };Rect b{ Center{}, pos, 30, 40 };

16
17
18
19

}

6
7

struct Center : Point

8{// コンストラクタの継承
using Point::Point;11 };9

列挙子によって動的に分岐する方法でも，コンパイ
ル時定数に基づく最適化でゼロオーバーヘッドにできる可能性がある．図形が複数種類ある場合，その性質

10
12
13

struct Rect

14{に合わせた選択肢を提供する列挙型を都度用意する15必要があり，ユーザコードはタグ・ディスパッチング

17と比較して少し冗長で文字数も増える．
1struct Rect

2

{

3

enum class Alignment

4{Center,
TopLeft,

5
6

};

7

Rect(TopLeft topLeft, int w, int h);16Rect(Center center, int w, int h);

18
19
20

// ...
};

21
22

int main()

23{
Rect a{ TopLeft{10,20}, 30, 40 };Rect b{ Center{10,20}, 30, 40 };

24
25
26

}

8
9
10
11
12
13

// Alignment:: を省略するため

しかし，そうしたクラスをジェネリックに扱うため

static constexpr auto
Center = Alignment::Center;static constexpr auto
TopLeft = Alignment::TopLeft;にクラステンプレート化すると，次のようにユーザ

14

Rect(Alignment a,
Point pos, int w, int h);

15
16

19

1

struct Point2{
int x, y;Point() = default;
Point(int _x, int _y);

3
4

17
18

コードで型を明示的に記述する必要が生じる．

// ...
};

5
6

};

7

20
21

struct Circle

8

struct PointF

22

{

9

{

23

enum class Alignment

10

24

{

11

Center,
TopCenter, // TopLeft は無い

25

float x, y;

};

12

};
28 };

14

template <class PointType>
struct TopLeft : PointType

15

{

29

16

26
27

13

30

int main()

17

31

{

18

using PointType::PointType;};

32

Rect a{ Rect::TopLeft,

19

33{10, 20}, 30, 40 };20

template <class PointType>
struct Center : PointType

21

{

34

Rect b{ Rect::Center,

35{10, 20}, 30, 40 };

36
37

}
これ以外に，座標クラスとそのコンストラクタを継承した，型情報に基準位置を持つクラスを作ることもusing PointType::PointType;

22
23

};

24
25

struct Rect

26

{

27
28

Rect(TopLeft<Point> topLeft,int w, int h);29考えられる．この方法は実引数の個数と記述を少なく

30

Rect(Center<Point> center, int w, int h);

2. 2 コードの表現力の向上

31// ...
33 };
32
34
35

int main()

36{
Rect a{ TopLeft<Point>{10,20}, 30, 40 };Rect b{ Center<Point>{10,20}, 30, 40 };

37
38
39

関数やパラメータの意味を推測しにくいコードは，間違いを生みやすい．次のコードは昔の Siv3D のコードであるが，挙動を正しく読み当てるのは難しい．
1

Timer timer{ 10, true };}正解は，構築と同時に「カウントがスタート」する

Siv3D では，ユーザのコストを抑えつつコンスト

10「秒」のタイマーであるが，時間の単位や第二引数

ラクタのバリエーションを増やすという課題に対し，の意味は，コメント無しでは伝わらない．このような呼び出し側がコンストラクタ引数とタグをセットにし問題が Siv3D の API の各所に見られたため，たオブジェクトを簡易に構築できる仕組みを考案し• ユーザ定義リテラルを積極的に活用し，数値リ

た [19]．その構文の見た目から「名前付き引数」と呼

テラルに単位の情報を付与する

ぶ．当然 C++のコア言語機能には本当の名前付き引• boolean literal を型安全で説明的な定数に置き数の仕組みはないため，疑似的に再現したものである．ソースコードを GitHub から入手できる [17]．
1struct Rect

2{
Rect(TopLeft_<Point> topLeft,int w, int h) {}

3
4

換える
という方針でコードの表現力を向上させた，現在のSiv3D では次のように記述しないとコンパイルでき
ない．
1

5

Rect(Center_<Point> center,
int w, int h) {}67Timer timer{ 10s, StartImmediately::Yes };2. 2. 1 ユーザ定義リテラルの活用

8

ユーザ定義リテラルは C++11 で導入されたコア// ...
};

言語機能で，指定したサフィックスの付いた数値・文12

int main()

字列リテラルについて，その値を引数とする関数の

13

{

9
10
11

Point pos{ 11, 22 };Rect a{ TopLeft = pos, 30, 40 };Rect b{ Center = pos, 30, 40 };Rect c{ TopLeft(10,20), 30, 40 };Rect d{ Center(10,20), 30, 40 };

14
15
16
17
18
19

}
名前付き引数によるオーバーロードにより，Siv3Dの長方形クラスや円クラスのコンストラクタは，様々な位置を基準にして図形を定義できるようになった．
線分クラスは，2 点の座標の組からだけでなく「始点糖衣構文として機能する特殊な文法である．例えば，
次のコードでは，_piサフィックスによって，円周率の X 倍を乗算演算子を使わずに得られる．
1

double operator ""_pi(long double x)

2{
return (x * std::numbers::pi);

3
4

}

5
6

int main()7{
double a = 0.5_pi; // a == 1.5707...

89}と，始点から終点へのベクトル」や「始点と，始点かC++標準ライブラリにおいても，数値リテラルをら終点への角度と距離」といった引数での構築も可能になった．

時間型に変換する sサフィックス，hサフィックスなど

1void Main()が定義されている．これらを活用することで，リテラ2{ルに単位が付与され，コードの情報量が増えるほか，

using namespace Arg;
Point pos1{ 11, 22 }, pos2{ 33, 44 };Rect a{ topLeft = pos1, 30 };Circle b{ topCenter = pos2, 30 };Line c{ pos1, pos2 };Line d{ pos1, direction(40, 50) };

3
4
5
6
7
8
9

}

関数呼び出しよりも短いコードで何らかの処理を行うことができる．Siv3D が提供するユーザ定義リテラルを表 1 にまとめ，実際のコードでの用法を次に示す．
1void Main()表1

サフィックス_pi

Siv3D が提供するユーザ定義リテラル

戻り値の型

double

引数の型

説明

long doubleπunsigned long long
_piF

ﬂoat

long doubleπunsigned long long_deg

doublelong double

度数法からラジアン角へ変換

unsigned long long
_degF

ﬂoat

long double

度数法からラジアン角へ変換

unsigned long long

2

_big

BigIntconst char*

多倍長整数

_bigF

BigFloat

const char*

多倍長浮動小数点数_dp

DecimalPlaces

unsigned long long

小数点以下の桁数を指定するマニピュレータ_fmt

FormatHelper

const char32*

Python スタイルの文字列フォーマットの適用_reRegExpconst char32*

文字列を正規表現オブジェクトへ変換_emojiEmojiconst char32*

文字列を絵文字オブジェクトに変換

_icon

Icon

unsigned long long{// 正方形を45 度回転させた図形を作成
Rect{ 10 }.rotated(45_deg);3
4

3

void F(bool formatDrive, bool enableLog)

4{if (enableLog) { }565// 文字列を画像ファイルパスとみなし,// 画像ファイルをオープン
Texture a{ U"image.png" };

6
7
8
9

// 文字列を絵文字とみなし,// 絵文字データを読み込む
Texture b{ U" 任意の絵文字"_emoji };

10
11
12

G(formatDrive);

7
8

}

9
10

int main()11{
constexpr bool EnableLog = true;

12
13

F(EnableLog, false); // 本当に正しい？

14

13// 多倍長浮動小数点数// （リテラル部分は文字列と見なされる）
auto c = 9.99999999999999999999_bigF;

14
15
16
17

// Python 風の文字列フォーマット
Print << U"{} {}"_fmt(U"Hello", 20);

18
19
20

// 正規表現のマッチ
U"(\\d+)-(\\d+)-(\\d+)"_re.match(U"2021-09-01");

21
22
23
24

整数をアイコン ID に変換

}

15

}
C++11 以降では，型付けのある enum class を使うことで安全にできるが，値を暗黙に bool 型に変換できないため，ライブラリの実装側の手間が増える．
1
2

enum class FormatDrive { No, Yes };enum class EnableLog { No, Yes };3
4void G(bool);5

2. 2. 2 強く型付けされたブーリアン型
boolean literal を関数の実引数で使うと，引数の取り違えやコードの可読性低下の原因となる．名前の付いた定数を用意することで意図を読み取りやすくな
るが，強い型付けは無く，取り違えの危険性は残る．
12void G(bool);7

void F(FormatDrive formatDrive,
EnableLog enableLog)

8

{

6

9

if (enableLog) { } // エラー

10
11
12

// 面倒
if (enableLog == EnableLog::Yes) { }

13
14
15

// 面倒
G(formatDrive == FormatDrive::Yes);

16

}

1

17

2

18

int main()

3

19

{

4

F(FormatDrive::No, EnableLog::Yes);

20
21

5

// タグ名は自由に決められる
using FormatDrive= YesNo<struct FormatDrive_tag>;using EnableLog= YesNo<struct EnableLog_tag>;

6// エラー//F(EnableLog::Yes, FormatDrive::No);

22
23

7

void G(bool) {}810void F(FormatDrive formatDrive,
EnableLog enableLog)

Siv3D では，このギャップを埋める次のようなユー

11{

ティリティクラスを実装した．説明のために実際の

13

24

}

9

コードから一部を簡略化している．

// 異なるタグのYesNo 間の変換は不可
2 template <class Tag>
1
3

struct YesNo

4

{

5

struct Helper { bool yesNo; };678explicit constexpr YesNo(bool yesNo)
noexcept: m_yesNo{ yesNo } {}if (enableLog) { }12G(formatDrive.getBool());

14
15

}

16
17

int main()18{
F(FormatDrive::No, EnableLog::Yes);

19
20// エラー//F(EnableLog::Yes, FormatDrive::No);

21
22
23

}

9
10
11

constexpr YesNo(Helper helper) noexcept: m_yesNo{ helper.yesNo } {}2. 3 C++17, C++20 規格の活用
Siv3D は動作要件のコンパイラを新しいバージョ

1215// contextually converted to bool// の文脈でbool 型の値としてふるまう
explicit constexpr operator bool() const

16

{

13
14

noexcept

18

API を，対応コンパイラの登場とほぼ同時に実装できる．C++17 や C++20 の機能の中で，特に Siv3D

return m_yesNo;

17ンに絞っているため，C++の新しい規格を活用する}への恩恵が大きかったものを紹介する．

19
20
21
22
23
24

// それ以外の文脈ではこの関数を使う
constexpr bool getBool() const noexcept{
return m_yesNo;
}

25
26
27

// 〇〇::Yes と記述できるように
static constexpr Helper Yes{ true };2. 3. 1 コンストラクタへの nodiscard 属性
Siv3D では，一時オブジェクトを作成したのちにメンバ関数で必要な操作を行うというパターンを多用するため，次の例のようにオブジェクトを作成しただ
けで何もしない誤ったコードをしばしば書いてしまう
問題があった．

28
29
30// 〇〇::No と記述できるように
static constexpr Helper No{ false };

31
32

35

void Main()2{// 無意味だが警告されなかった
Rect{ 100, 50, 40, 30 };3

private:

33
34

1

4
5

bool m_yesNo;};

Rect{ 100, 50, 40, 30 }.draw();

6
7}

if ()のような contextually converted to bool の文

C++17/20 でコンストラクタへの [[nodiscard]]

脈で bool 型の値のようにふるまうため，enum class

属性 [14] が有効になったため，Siv3D では原則としてを使う方法と比較し，値を使用する箇所の変更が最小

すべてのコンストラクタに [[nodiscard]]属性を付与

限で済む．主要なコンパイラの最適化を有効にしたビ

し，前述のようなケースでコンパイラが適切な警告

ルドにおいて，単純に bool 型の値を使用したときとメッセージを発するよう改善した．

同じアセンブリが生成されることを確認した．

2. 3. 2 Designated Initialization

引数に渡した際に生じる難解なエラーメッセージを

C++20 から，集成体 (aggregates) に対してメンバ回避するために SFINAE を用いていたが，Concepts

変数名を指定した初期化 (designated initialization)

によりコードが単純化された．C++20 への移行期でが可能になった [13]．これまでは，メンバ変数の多い

Concepts が未サポートのコンパイラ向けのコードと，構造体を初期化する場合，集成体初期化で一斉にパラ

Concepts 対応コードをライブラリに共存させるため，

メータを記述するか，初期化後に名前を使って値を代

Siv3D では次のようなマクロを定義して利用した．

入するかの二択であった．前者は同じ型の値を取り違1えても気付かない，後者は構造体を const にできず，

2

また，一部のメンバ変数の初期化を忘れる可能性があ4

るという欠点があった．こうした問題が designatedinitialization によって解決された．

// Concepts をサポートするコンパイラ用# define CONCEPT_INTEGRAL template <std::
integral Integral>
5 # define CONCEPT_INTEGRAL_ CONCEPT_INTEGRAL
3

6
7

1

struct BoundingBox2{#if __cpp_lib_concepts

#else

8

9

// Concepts をサポートしないコンパイラ用// - SFINAE イディオムを使用
11 # define CONCEPT_INTEGRAL template <class
Integral, std::enable_if_t<std::
is_integral_v<Integral>>* = nullptr>
12 # define CONCEPT_INTEGRAL_ template <class
Integral, std::enable_if_t<std::
is_integral_v<Integral>>*>

10

13

3
4
5
6
7

9

double xMin = Math::Inf;
double yMin = Math::Inf;
double xMax = -Math::Inf;
double yMax = -Math::Inf;};

8// - 通常の集成体初期化// どの値がどのメンバ変数にセットされるか,
11 // この局所的なコードだけではわからない
12 BoundingBox A()
13 {14
return{ 0.1, 0.2, 0.3, 0.4 };
15 }

10

14
16
17

// - あとで値を代入
18 // 値をセットし忘れたメンバ変数があっても19 // 気付かない
20 BoundingBox B()
21 {22
BoundingBox b;23
b.xMin = 0.1; b.yMin = 0.2;
24
b.xMax = 0.3; b.yMax = 0.4;
25
return b;
26 }
17

20
21
22

// Designated Initialization
BoundingBox C()
30 {31
return{32.xMin = 0.1, .yMin = 0.2,
33
.xMax = 0.3, .yMax = 0.4
34
};
35 }

// _ 付きは，SFINAE 版での// デフォルトテンプレート引数の再定義回避用
CONCEPT_INTEGRAL_
auto Square(Integral n) { return n * n; }23
24

int main()25{// 読みやすいエラーメッセージが出力//Square(0.5);

26
27
28

Square(10);

29
30

27

CONCEPT_INTEGRAL
auto Square(Integral n);

18
19

16

#endif

15

}

28
29

2. 4 サブシステムの初期化と終了処理
Siv3D のように多くのサブシステムから構成されるライブラリの一般的なデザインパターンの 1 つは，各サブシステムをシングルトンクラスとすることで

2. 3. 3 Concepts

ある．サブシステムには依存関係があり，正しい順番

C++20 の Concepts の登場まで，テンプレートので初期化と終了処理が行われる必要があるため，工夫

型に制約を加えるには，SFINAE と呼ばれる複雑なが必要である [1] [7]．

イディオムを使う必要があった．以前より Siv3D でSiv3D では，サブシステム管理の設計にあたって，は，型制約を用いたオーバーロードが必須でない単サブシステムを追加する際のコードの変更を最小に純なテンプレートにおいても，ユーザが誤った型をするようなクラスを設計した．すべてのサブシステム

を単一の std::tupleにまとめて格納することが特徴

14

で，std::get()を個別の getter の代わりにでき，ま15

た，サブシステムの初期化と終了処理のタイミングを

17

短いコードで一括して制御できるという利点がある．

Manager() noexcept;

16~Manager();18

次に示すコードでは，例として IRendererと IAudio

20

template <class Interface>static auto* Get() noexcept

21{というサブシステムを管理している．ここに新しくサ22

ブシステムを追加したい場合，

19

25• サブシステムを tuple の要素に追加

27という 3 行の変更だけで済む．

}

24• Manager.hpp に前方宣言を追加• Manager.cpp でヘッダをインクルード

return std::get<SubSystem<Interface>>(pManager->m_subsystems).get();23

26

private:inline static Manager* pManager = nullptr;

28
29// サブシステムをこのtuple に追加
std::tuple<
32
SubSystem<IRenderer>,
33
SubSystem<IAudio>>34
m_subsystems;
35 };
30
31

// SubSystem.hpp#pragma once
3 #include <cassert>
12
4
6

template <class ISubSystem>
class SubSystem

7

{

8

public:5

9~SubSystem(){// Manager::~Manager() によって破棄済み
assert(pSubSystem == nullptr);
}

10
11
12
13
14

36

// 短いコードでサブシステムに// アクセスできる便利なマクロ39 #define GET(SUBSYSTEM) Manager::Get<I##
SUBSYSTEM>()
37
38

1
2

// Manager.cpp#include "Manager.hpp"

3// サブシステムのヘッダ#include "IRenderer.hpp"
6 #include "IAudio.hpp"
4

15
16

ISubSystem* get() noexcept

17{
return pSubSystem;

18

}

19

5
7
9

template <size_t I, class Tuple>
static void Release(Tuple&& t)

{

8

20
21

void release()

10

22

{

11

delete pSubSystem;
pSubSystem = nullptr;

23
24

}

25

std::get<I>(t).release();
if constexpr (I > 0)
Release<I - 1>(t);

12
13
14

}

15

26
28

17

template <class Tuple>
static void ReleaseAll(Tuple&& t)

29// ファクトリ関数でサブシステムを生成
ISubSystem* pSubSystem
31= ISubSystem::Create();
32 };

18

{

30

19

27

private:

16

21
22

// Manager.hpp
2 #pragma once
3 #include <tuple>
4 #include "SubSystem.hpp"
1

5// 前方宣言だけで OK.
7 // サブシステムのコード変更の影響を受けない
8 class IRenderer;
9 class IAudio;
6

10
11

class Manager

12

{

13

public:return Release<std::tuple_size_v<std::remove_reference_t<Tuple>>-1>(std::forward<Tuple>(t));

20

}

23
24

Manager::Manager() noexcept

25{
pManager = this;

26
27

}

28
29

Manager::~Manager()30{// サブシステムを初期化と逆順ですべて終了
ReleaseAll(m_subsystems);
pManager = nullptr;

31
32
33
34

}

// IRenderer.hpp
2 #pragma once

拡張すると，グラフィックスを描画するプログラムを

1
3
4

class IRenderer // サブシステムの例

5

{

6

public:

方法）や使用するシェーダプログラム（GPU 用のプ
ログラム）の切り替え，座標変換行列のスタック操作

7
8

組む際に頻繁に登場する，レンダーステート（描画のをコードに記述するうえでも便利である．Siv3D でstatic IRenderer* Create();は，レンダーステートを，そのオブジェクトのスコー

virtual ~IRenderer() = default;

プが有効な間だけ適用する ScopedRenderStates2Dク

9
10
11

virtual void draw() = 0;

12
13

};

// IAudio.hpp
2 #pragma once
1
3
4class IAudio // サブシステムの例

5

{

6

public:ような Transformer2Dといったクラスを提供する．グ
ローバルなレンダーステートに行った変更がデストラ
クタで自動的にリセットされるため，関数の呼び出し

くなり，プログラムの見通しが良くなる．これらのク

static IAudio* Create();ラスの変数はコード中では明示的に使われず無意味

virtual ~IAudio() = default;に見えるが，実際はデストラクタが仕事をする．

virtual void play() = 0;1void A()

2

{

9
10

の行列スタックに push し，デストラクタで pop する

先で行われた変更が呼び出し元に影響することが無78ラスや，コンストラクタで新しい座標変換行列を現在

11
12
13

};

ScopedRenderStates2D s{
BlendState::Additive,
SamplerState::ClampNearest };3
4

1// Library.cpp#include "Manager.hpp"
3 #include "IRenderer.hpp"

5

2

6

4

8

5

void DrawSomething()

Draw();} // s のデストラクタが9// レンダーステートをリセット

6

{

10

// IRenderer に直接アクセス
GET(Renderer)->draw();

7
8
9

}

7

11

void B()

12

{

10

14

11

int main()

15

12

{

16

13
14

// シングルトンオブジェクトを作成
Manager mgr;

15
16
17

A();

13

// ここではA() の中での// レンダーステート変更の影響を受けない
Draw();

17
18

}

19

DrawSomething();} // デストラクタでサブシステムを順番に破棄

20

void C()

21

{

22
23

// スケールをpush
Transformer2D t1{ Mat3x2::Scale(2.0) };24

2. 5 RAII を利用したレンダーステート管理
Resource Acquisition Is Initialization (RAII) は，
オブジェクトの初期化と破棄にリソースの確保と解放を関連付ける，C++ の基本的なイディオムである．標準ライブラリでは std::fstreamや，std::lock_guard
で活用されていてる．Siv3D においても，テクスチャ，

25
26
27
28
29
30
31
32
33

音声データなど，リソースを管理するクラスで必ず使34われている．

36

RAII における管理対象をリソースからステートに// 2 倍のスケールで描画される
Draw();{// スケールをpush
Transformer2D t2{ Mat3x2::Scale(1.5) };// 3 倍のスケールで描画される
Draw();} // t2 のデストラクタがスケールをpop// 2 倍のスケールで描画される
Draw();37 } // t1 のデストラクタがスケールをpop
35

2. 6 ヘッダファイルのドキュメント性の向上

特化したヘッダファイル（拡張子 .ipp）を，インライC++の IDE は，関数や定数の宣言箇所に記述されン関数を持つほとんどのヘッダファイルに追加した．

たドキュメントを認識し，コーディングのヒントとし導入後，.hpp ファイルには基本的にドキュメントとてコードエディタ上にツールチップ等で表示する機能

インタフェースのみが記述されるようになった．

を有する．また，コードエディタ上でプログラマが関

ただし，インライン関数の実装のすべてを .ipp ファ数や値をクリックして，実際の宣言箇所へジャンプでイルに移動することはできない．Hidden Friedns [2]

きる機能もある．こうしたことから，ライブラリのの関数の実装をヘッダ内のクラス定義の外に記述す

ヘッダファイルにはユーザのための情報が適切に記述ると，そのヘッダをインクルードした翻訳単位ではされていることが望ましい．Siv3D のヘッダファイル

Hidden Friends としての効力が失われる．部分的な解の書き方について採用した方針を説明する．

決方法として，Hidden Friends の関数の実装を .cpp

2. 6. 1 ドキュメンテーションタグ

ファイルに記述することができる．その場合，関数を2021 年現在の主要なコードエディタで最も広くサ実装した翻訳単位（通常はユーザに公開されないライポートされている C++ ドキュメンテーションタグ

ブラリの内部コード）においては Hidden Friends との記述方式は Doxygen である（そのほかに，一部しての効力を失うが，ヘッダをインクルードしたソーのコードエディタでサポートされる記述方式としてスファイルでは Hidden Friends としてふるまう．な

XML コメント [8] がある）．

お，インライン化が必要な constexpr 関数ではこの

Siv3D では，Doxygen タグのうち，関数やクラス解決方法が使えないという制約があるため，必要に応の機能の概要を説明する@briefタグ，引数を説明する

じて一部の Hidden Friends であるメンバ関数の定義@paramタグ，テンプレート引数を説明する@tparamタグ，

は .hpp ファイルに残されたままである．

戻り値がある場合にその意味を説明する@returnタグ.ipp ファイルの導入前後で，各ファイルに含まれるをすべての API に記述することを目標とした．必要にコードがどのように変化するか，説明用のクラス Aを応じて@remarkタグで，関数の副作用や使い方の注意な用いたサンプルコードを示す．差分に注目されたい．

ど補足の説明を行った．例えば，std::basic_string::.ipp ファイル導入前:

size()と std::basic_string::length()がそうである

1

ように，同じ挙動をする別名の関数については，ユー

2// A.hpp/// @brief ドキュメント

3class A

ザが混乱しないよう，お互いが同じものであることを

4

{

5

public:

明記した．

2. 6. 2 public メンバと private メンバの順序

678

ヘッダに記述するクラスの public メンバを private

9

メンバよりも前に配置することで，ヘッダファイルを10

閲覧したユーザが，必要とするドキュメントに早くた12どり着けるようにした．

2. 6. 3 .ipp ファイルの導入

11/// @brief ドキュメント
constexpr A(int a, int b): m_data{ a * b } {}

13
14
15
16

ヘッダファイルに記述されるインライン関数の実装/// @brief ドキュメント
A() = default;

17/// @brief ドキュメント
constexpr int getData() const noexcept{
return m_data;}

は，ヘッダファイルの行数を増加させる．ユーザの関18

心が高いインタフェースやドキュメンテーションが画

20/// @brief ドキュメント

21

constexpr void increment() noexcept

22{

面に出現する頻度が低下し，一覧性や検索性が損な

19

われる．Siv3D では当初，.hpp ファイル + .cpp ファ

23

イルの構成でこのような問題があったことから，ファ25

イル構成を見直し，インライン関数の定義の記述に

2426++m_data;}/// @brief ドキュメント

27

void update();29/// @brief ドキュメント

30

friend std::ostream& operator << (std::
ostream& os, const A& a);

28
29
30
31

friend constexpr bool operator ==(int lhs,
const A& rhs) noexcept

31

{

33

return (lhs == rhs.getData());

32

}

33

/// @brief ドキュメント
friend std::ostream& operator << (std::
ostream& os, const A& a);

36
37
38

private:int m_data;40#include "detail/A.ipp"

1// A.ipp2

constexpr A::A(const int a, const int b): m_data{ a * b } {}

};

4
5

constexpr int A::getData() const noexcept

6

{

2

// A.cpp#include "A.hpp"

3
4

void A::update()

5

{

}

9
10

constexpr void A::increment() noexcept11{++m_data;

12
13

}

1

// A.cpp#include "A.hpp"// ...

6
7

return m_data;

7
8

1

int m_data;

};

36

3

39
41

34
35

34
35

private:

32

}

2

8
9

std::ostream& operator << (std::ostream& os,
const A& a)

3
4

void A::update()

10

{

5

{

return os << a.getData();

11
12

}

// ...

6
7}

8.ipp ファイル導入後:
2// A.hpp/// @brief ドキュメント

3class A

4{

5

public:

1

6
7
8

11

14

constexpr void increment() noexcept;

18/// @brief ドキュメント
void update();性が改善される．またクラス A のインライン関数が，• A.hpp → B.hpp
導入後は• A.hpp → B.hpp → A.ipp → B.ippの順でインクルードすることにより問題なくコンパイルできる．依存関係のためにヘッダ内で constexpr
化できなかったメンバ関数を constexpr 化できるよ21/// @brief ドキュメント

23

friend constexpr bool operator ==(int lhs,
const A& rhs) noexcept

24{
return (lhs == rhs.getData());

25

}

27
28

.ipp ファイルの導入はライブラリコードの総量をの順でのインクルードはできないが，.ipp ファイルの/// @brief ドキュメント

26

return os << a.getData();}/// @brief ドキュメント
constexpr int getData() const noexcept;

17

22

12

別の同様に書かれたクラス B に依存していた場合，

16

20

{

11

/// @brief ドキュメント
constexpr A(int a, int b);

15

19

10

/// @brief ドキュメント
A() = default;

12
13

std::ostream& operator << (std::ostream& os,
const A& a)

増大させるが，ユーザ視点から見るとヘッダの一覧

9
10

9

/// @brief ドキュメント

うになり，ライブラリ設計面でのメリットがある．

3 ユーザコミュニティの運営
本節では，Siv3D のユーザコミュニティを運営，拡大するためにとった施策と，その成果を報告する．

3. 1 イベントの実施
Siv3D の普及とユーザコミュニティの発展のために，次のイベントを継続して開催している．

3. 2 コミュニティ運営に利用したサービス
Siv3D のユーザコミュニティの発展に役立ったツールを紹介する．

勉強会 Siv3D の開発環境のセットアップ方法やSlack 登録しているメンバーとメッセージやファチュートリアルを解説する，2〜6 時間のイベン

イルをやり取りできるグループウェア．ユーザがト．数名〜数十名が参加する．年に数回の頻度で質問を投稿すると誰かが回答してくれる質問チャ

開催．

ンネルや，特定の新機能の開発に関心のあるメン

実装会 Siv3D のアクティブユーザおよびコミッ
タを対象に，コードレビューや技術的な助言，開発ツールの使用法指導などを行う．数名〜十数名が参加する．毎月開催．

バーが進捗報告や技術的な相談をするチャンネルなどが活発である．

Discord 無料で利用できるビデオ通話サービス．
若い世代にはコンピュータゲームプレイ時のボイ

チャレンジ Siv3D に実装したい新機能を課題形

スチャットサービスとして普及している．画面共式にしてまとめ，ユーザコミュニティに解決をし有機能を持つため，遠隔で動作を確認しながらのてもらう，オンラインで数カ月間にわたって開催

サポートにも活用できる．Siv3D コミュニティでするイベント．年に 1 回の頻度で開催．

は，2020 年のパンデミック以降，オンラインの地方各地で開催している勉強会は，現地の大学のサークルやプログラミング教室など，ローカルのコ

勉強会や実装会を開催する場合のプラットフォー
ムとして活用機会が増えた．

ミュニティと協力して開催することで，会場の手配やWhereby Web ブラウザ上で追加のアプリのイ

集客を手伝ってもらっている．実装会は，参加人数をンストールや，ホスト以外のアカウント登録が不絞る代わりに一人あたりに多くの時間を割いて，技要で使えるビデオ通話サービス．画面共有機能を術サポートや高度な使い方の講習を行う．現時点で持つ．1〜2 人との小規模な会議で用いた．

Siv3D のコードの執筆者は 23 名いるが，そのうち実

GitHub Sponsors ソースコードホスティング

装会参加後にコミッタとなったメンバーは 16 名と，サービス GitHub 上で 2019 年に始まった，OSS

効果を上げている．どちらも 2020 年のパンデミック

開発者を金銭的に支援できるサービス．支援の対以降は，オンラインでの開催にシフトしている．

価として氏名の掲載や個別のユーザサポートを「チャレンジ」は，Google Summer of Code を参設定するケースが多い．Siv3D はこのサービス考にしたイベントで，Siv3D に実装されると便利でを通して，最初の 2 年間で約 2,000 米ドルの資ある様々な機能の実装課題をリストアップし，ユーザ金を得た．

コミュニティの有志に挑戦してもらうという取り組みである．参加者どうしがコミュニケーションできる専4 開発ツール

用のグループチャットを用意したり，技術的なサポー

4. 1 C++開発ツール

トやコードレビューを頻繁に提供したりすることで2000 年代後半以降，C++は他のプログラミング言

参加者をサポートした．課題の発表時点では具体的語と比較して，開発支援ツールの発展が見劣りする状な解決法の見当がついていないものもあったが，参加況であったが，近年はいくつかのツールの登場により者の調査や研究によっていくつもの課題が達成され状況が改善している [15]．

た．例えば「世界地図や日本地図を表示できるように大規模な C++ プロジェクトである Siv3D においしたい」という目標は，地理空間情報を記述するテても，一般的な IDE (Visual Studio や Xcode) の機

キストフォーマット GeoJSON のパーサと，読み取っ能に加え，C++コーディングの生産性を高めるためたデータを Siv3D の基本図形クラス Polygon 型に変の様々なツールを活用した．それらの機能と用途を紹換する関数の実装によって実現された．

介する．

Wandbox [23] 数十種類以上の C++ コンパイ

4. 2 Web プラットフォームへの対応

ラによるコンパイルと，コードの実行結果を確認

Siv3D の C++コードを入力にして，Web ブラウ

できるオンラインコンパイラ．コンパイラごとのザで動作する JavaScript コードを得るツールチェー

C++コードの解釈の差異の検証と，エラーや警ンを，Emscripten などを利用して整備した．詳細な

告メッセージの確認のために使用した．

構成は Siv3D のコードリポジトリのビルド手順かCompiler Explorer [3] 様々なバージョンのコら確認できる．これにより，Siv3D のプログラムがンパイラやコンパイルオプション設定で C++

従来の PC だけでなく，モバイルを含むより広いプ

コードをコンパイルし，そのアセンブリ出力をラットフォームで動作するようになった．現在 Google

確認できるオンラインコンパイラ．抽象度の高い

Chrome, Microsoft Edge, Mozilla Firefox などの主C++コードを書く際，各コンパイラでオーバー

要 Web ブラウザの最新版での動作を確認している．

ヘッドが生じないかを確認するために使用した．

ネイティブアプリケーション専用の命令を使用してQuick C++ Benchmark [21] 複 数 の 異 な るいないなど，条件が良い場合，既存のプログラムを 2

コードの実行時間を相対的に比較して可視化〜3 行変更するだけで Web ブラウザで動作するようする，ベンチマーク機能を備えたオンラインコンになる．過去に Siv3D で制作したゲーム作品を Web

パイラ．バックエンドに Google Benchmark [5]

ブラウザでの動作に対応させて再公開するユーザもを使用し，その API である，コード中の未使用見られた．

変数の最適化を抑止する命令などが提供される．

Web 対応にあたっては，従来の Linux 版のコー

Siv3D のコードに近似計算やアルゴリズムの変ドをベースに OS ネイティブの命令を Web ブラウ

更を導入する際のプロファイリングに使用した．

ザで実行可能なコードに置き換える作業や，既存のPVS-Studio [10] C++ や C#, Java を対象とOpenGL4 レンダリングエンジンをベースに WebGL

した静的コード解析ツール．実行時性能に影響す

2.0 レンダリングエンジンを新しく実装・追加するな

るコード，冗長なコード，タイプミス，バグが疑ど，70 のソースファイルの追加が発生したが，全体われる箇所等を検出できる．
こうしたツールにより Siv3D のコードの問題が検出から見れば少ない割合の変更で Web への移植が実現したことは特筆すべきである．

された事例を紹介する．次のコードでは，add()の第一引数で move されているオブジェクトの use-after-

5 今後の課題と，C++ やツールへの要望

move が，_fmt()内で発生する．PVS-Studio によっ

本節では，これからの Siv3D で解決したい課題と，て検出された．
1
2
3
4

m_fonts.add(std::move(font),
U"({0} {1} size: {2})"_fmt(
font->getFamilyName(),font->getStyleName(), fontSize));

次の例は目視で気付きにくいミスである．6 行目のリストの最初の要素は bezier.p0.yが正しい．PVS-

Studio により，疑わしいコードとして検出された．
1
2
3
4
5
6
7
8

const double minX = std::min({bezier.p0.x, bezier.p1.x, bezier.p2.x});
const double maxX = std::max({bezier.p0.x, bezier.p1.x, bezier.p2.x});
const double minY = std::min({bezier.p0.x, bezier.p1.y, bezier.p2.y});const double maxY = std::max({bezier.p0.y, bezier.p1.y, bezier.p2.y});C++や開発ツールが将来どのように進化・整備されると望ましいかについて，Siv3D の開発・運用経験を踏まえた提言を行う．

5. 1 Siv3D の今後の目標・課題
5. 1. 1 Web 版のサイズ圧縮
現在の Web 版 Siv3D は，PC 版とほぼ共通のコードやアセットファイルを使用するため，実行時に必要となるファイルサイズについてはあまり考慮されていない．ネットワーク速度に依存するが，現在のWeb 版の Siv3D アプリケーションは，Web ブラウ
ザでページにアクセスしてから実行までに平均で 5〜

15 秒前後かかり，その大半はファイルのダウンロー

ドに要している．

5

これらの改善のために，機能が制限されるが必要

6

なファイルサイズがコンパクトになるようなビルド

8オプション，単一のファイルで複数のウェイトや属性のフォントを生成できる variable fonts の使用による{
std::mt19937 rng{ 12345 };7

std::uniform_int_distribution<int>
ud{ 1, 6 };910

std::cout << ud(rng) << ’\n’;

11
12

}

フォントサイズ圧縮などを検討している．
これらの機能に関して，Siv3D では標準ライブラ

5. 1. 2 サブセットライブラリ
Siv3D はプログラムのエントリポイントのほか，サリに依存せず，代替となる共通の実装を用意した．乱ブシステムの初期化や状態更新，連携をライブラリ側

数分布については専門的な数学知識が要求されるたのコア部分が担っている．各種 API とコア部分の依め，自前での実装は行わず，ヘッダオンリーで必要な存関係が密接であるため，Siv3D が提供する C++の機能がそろう Abseil C++ library [4] を用いた．

ユーティリティクラスや文字列，画像処理など，一部

この問題は標準ライブラリの範疇で解決できればだけをその機構から独立に抜き出して再利用するこメリットが大きい．std::portableといった名前空間とが難しい．つまり，Siv3D を使っていない既存のプに，移植性が保証された代替関数を提供することが考ロジェクトにおいて，Siv3D の一部の機能だけを使えられるだろう．

いたい場合，Siv3D がエントリポイントを握るプロ

5. 2. 2 constexpr 数学関数

グラムのほうに既存のコードを持ち込むという主従

平面の法線，光源の方向などの 3 次元ベクトルに関係が要求される．

ついて，軸に平行でない単位ベクトルを，次のようこの制約は Siv3D のコード資産が多くのプロジェなコードでコンパイル時定数にしたいが，ベクトル

クトで活用される機会を狭めている．Siv3D が管理正規化の計算で使われる平方根関数 std::sqrt()はするサブシステムに依存しないスタンドアローンな

constexpr でないためコンパイルできない．

API だけを切り出したサブセットライブラリを提供

1

できると望ましい．サブセットライブラリの利用者が増えることで，その部分の改良やテストが加速し，それが本体に反映される効果も期待される．

2

constexpr Vec3 v= Vec3{ 1.0, 1.0, 1.0 }.normalized();3DCG に関するライブラリ API は，Siv3D に限らず，計算量の節約のため，正規化されているベクトル
を入力の前提条件としている場合が多い．ベクトル正5. 2 C++ への要望

規化の計算を constexpr 化できれば，より多くの計算

5. 2. 1 移植性のある乱数分布，シャッフルの実装

をコンパイル時に評価でき，実行時性能やコードの記

C++の規格では，標準ライブラリの乱数分布や範述性の向上につながる．

囲要素のシャッフルに使われる細かいアルゴリズムを

C++23 以降に向けて，標準ライブラリの一部の数規定していないため，疑似乱数生成エンジンに同じ学関数の constexpr 化が提案されている [12] ため，進シードを与えて乱数ソースの再現性を確保しても，処展に期待したい．

理系によって異なる実装を持つ乱数分布やシャッフル

5. 2. 3 UTF-16, UTF-32 サポートの拡充の操作を通過することで実行結果が一致せず，プログ

ひらがなや漢字を含む文字列について，各文字へのラムの移植が妨げられる問題がある．
標準の C++では，次のような単純なコードでさえ処理系によって実行結果が異なる．
1
2

う，現在の Siv3D では UTF-32 を基本の文字コードとしている．当初の Siv3D は Windows 版から開発が#include <iostream>#include <random>

始まった歴史的経緯で，Windows のネイティブ API

int main()を採用していたが，wchar_t型は処理系により文字

3
4

ランダムアクセスをシンプルなコードで記述できるよと互換性のある wchar_t型 (Windows では UTF-16)

コードおよびサイズが異なるため移植性に難があり，途中からは，文字コードが規定されている型に移行することにした．UTF-8 は空間効率に優れるが，ひらがなや漢字，さらに複数のコードポイントからなる絵文字を含む場合の取り扱いが難しかったため UTF-32

表2

Siv3D に登場する用語とその言い換え例

用語

言い換え例

描画

表示，描く，出す，出力

音声

オーディオ，サウンド，音楽，

BGM (background music)，を選んだ．Siv3D のプログラムの文字列リテラルに効果音，SE (sound eﬀect)

使われるプレフィックス Uは char32_t型の文字列リテ
透過

半透明，透ける

座標

位置，場所，地点

動画

ビデオ, ムービー

除き，char型，よくても wchar_t型までのサポートしテキスト

文字列，文章，メッセージ

か保証されていない．標準ライブラリ API のクラス

フレームレート

FPS (frames per second)ラルであることを示す．
残念ながら，C++標準ライブラリの文字列処理 APIは，std::basic_stringや std::basic_string_viewを

テンプレート引数にこれら以外の型を指定してもコンパイルに問題が生じるケースがほとんどである．正Unicode Emoji は例年 100〜300 文字前後のペースで

規表現，std::format, 標準入出力などで，char16_t,

追加されており [22]，フォントファイルのサイズもまchar32_tがサポートされると，文字列に UTF-8 以外

すます大きくなっていくだろう．カラー絵文字を現在を採用するライブラリ開発者の負担が減る．

よりも小さいサイズで保存できるベクター画像形式[6] の普及や，ファイルサイズを優先してデザインさ

5. 3 開発・整備が望まれるツールの提言れた絵文字の登場が望まれる．

5. 3. 1 テスト用ファイルのコレクション

5. 3. 3 言い換え表現に強いドキュメント検索

各種ファイル形式の読み込みの検証に使える，あらユーザが特定の操作や機能の実現方法をドキュメン

ゆる種類・内部形式のテストファイルが一か所に集約

トやライブラリヘッダ，Web で検索する際，言い換えされていると便利である．例えば JPEG ファイルで表現がヒットせず，別の用語を使っていれば得られてあれば，画像の大きさが最小のもの，最大のもの，画いたはずの情報にたどり着けないことがある．Siv3D

像データの内部フォーマットが特殊なもの，Exif デー

プログラミングで頻繁に使われる用語の中から，言いタを含むものなど，ユーザ環境で登場しうるあらゆる換え表現が存在する例を表 2 にまとめた．

バリエーションのファイルが用意され，プログラムがこうした言い換え表現を検索時のヒントとして登それらを適切に扱えるかベンチマークできるファイ

録し，検索結果に反映してくれるドキュメンテーショ

ル群である．こうしたものを，画像以外も含む数百種

ンジェネレータや検索エンジンがあると便利である．

類以上のファイル形式について網羅し，開発者がア
クセスしやすい形で提供されると，Siv3D のように，

6 おわりに様々なファイル形式を扱うライブラリ開発者の助けに

本稿では，Siv3D の実例を通して，C++ライブラ

なる．

リ API 設計のテクニックと，開発や普及に関する経

5. 3. 2 軽量なカラー絵文字フォント

験を説明した．C++の言語の進化は，C++14 からSiv3D では移植性のために，あらゆるプラットフォー

C++20 にかけての規格書のページ増分だけで測ってムで，共通のカラー絵文字の TrueType フォントファ

も 495 ページと膨大である．ライブラリ開発者がユー

イルをアプリケーションに同梱している．ファイル

ザの期待や，言語の要求する水準に応えていくためにには現時点で約 3,600 文字が収録され，サイズは 8.6

は，設計技法や開発生産性向上に関する議論と知見のMB（Zstandard 圧縮後）と，アプリケーションを構

共有がますます必要になっていくだろう．本稿の事例

成するファイル群の中でも一番の容量を占めている．

がそのような流れの一助になれば幸いである．

参 考 文 献[ 1 ] Bilas, S.: An Automatic Singleton Utility, Game
Programming Gems. Mass: Charles River Media,
Inc, (2000).
[ 2 ] Brown, E. W. and Sunderland, D.: Recommendations for Specifying ”Hidden Friends”, Technicalreport, March 2019. ISO/IEC JTC1/SC2/WG21:
C++ Standards Committee paper P1601R0.
[ 3 ] Godbolt, M.: Compiler Explorer (website).
https://godbolt.org/, (also at Internet Archivehttps://web.archive.org/web/20210803150815/
https://godbolt.org/).
[ 4 ] Google: Abseil C++ library (code repository).
https://github.com/abseil/abseil-cpp.
[ 5 ] Google: Benchmark (code repository). https://github.com/google/benchmark.
[ 6 ] Google: IconVG (code repository). https://github.com/google/iconvg.
[ 7 ] Gregory, J.: Game engine architecture, crc
Press, 2018.
[ 8 ] Microsoft: XML Documentation. https : / /docs.microsoft.com/en-us/cpp/build/reference/
xml-documentation-visual-cpp?view=msvc-160.
[ 9 ] Nystrom, R.: Game programming patterns,
Genever Benning, 2014.
[10] PVS-Studio: PVS-Studio (website). https ://pvs-studio.com/en/pvs-studio/, (also at Internet Archive https : / / web . archive . org / web /20210724220841 / https : / / pvs-studio . com / en /pvs-studio/).
[11] Reddy, M.: API Design for C++, Elsevier,
2011.
[12] Rosten, E. J. and Rosten, O. J.: constexpr
for <cmath>and <cstdlib>, Technical report, June
2021. ISO/IEC JTC1/SC2/WG21: C++ Standards
Committee paper P0533R8.
[13] Shen, T. and Smith, R.: Designated Initialization Wording, Technical report, July 2017. ISO/IEC JTC1/SC2/WG21: C++ Standards Committee paper P0329R4.
[14] Sommerlad, P.: [[nodiscard]] for constructors,

Technical report, July 2019.
ISO/IEC JTC1/
SC2/WG21: C++ Standards Committee paper
P1771R1.
[15] Stroustrup, B.: Thriving in a crowded andchanging world: C++ 2006–2020, Proceedings of
the ACM on Programming Languages, Vol. 4,
No. HOPL(2020), pp. 1–168.
[16] Stroustrup, B. and Sutter, H.: C++ Core
Guidelines (website). https : / / isocpp . github .
io/CppCoreGuidelines/CppCoreGuidelines, (alsoat Internet Archive https://web.archive.org/
web/20210803200203/https://isocpp.github.io/
CppCoreGuidelines/CppCoreGuidelines).
[17] Suzuki, R.: siv::NamedParameter (code repository).
https : / / github . com / Reputeless /NamedParameter.
[18] Suzuki, R.: ちょっと変わったプログラミング教室プログラミングで絵, 音, 3D 物体を描こう, 動かそう-:
2. 音や画像で遊ぼう-インタラクティブアプリケーショ
ンための C++ フレームワーク「Siv3D」, 情報処理,
Vol. 58, No. 6(2017), pp. 474–480.
[19] Suzuki, R. and Choh, I.: C++14 における名前付
き引数の実装, 情報処理学会第 79 回全国大会講演論文
集, Vol. 2017(2017).
[20] Suzuki, R. and Choh, I.: Siv3D: インタラクティブ
アプリケーションのための C++ フレームワーク (サイバー増大号)–(特集 ソフトウェア論文), コンピュータソフ
トウェア= Computer software, Vol. 34, No. 4(2017),
pp. 17–38.
[21] Tingaud, F.: Quick C++ Benchmark (website). https://quick-bench.com/, (also at Internet Archive https : / / web . archive . org / web /20210620011638/https://quick-bench.com/).
[22] Unicode: Emoji Versions (website). https://
unicode.org/emoji/charts/emoji-versions.html,(also at Internet Archive https://web.archive.
org/web/20210729211342/https://unicode.org/
emoji/charts/emoji-versions.html).
[23] Wandbox: Wandbox (website).
https : / /wandbox.org/, (also at Internet Archive https://web.archive.org/web/20210708011025/https://wandbox.org/).