見出し画像

MGL週報 #72 - gccでビルドが通らない問題の対策

このエントリはゲーム開発用フレームワーク「MGL」の開発記録です。MGLはzlibライセンスの下に無償で提供されています。


gccでビルドが通らない問題の対策

既知の問題として挙げられている通り、現在MGLではgccでビルドが通らないという問題に直面しています。こちらの解決の目処が立ちつつあるため、その対策について書き残しておきましょう。

constexprが通らない問題

こちらは過去に何度か述べている内容ですが、clangやMSVCではC++17でも定数式内で非定数式が呼び出せてしまいます。

詳細はこちら。

対策としては、今回はとりあえずC++23への移行を見越して次のようなマクロを用意しました。

// C++23以降で constexpr になるマクロの定義(C++23に完全移行するまでの繋ぎ)
#if __cplusplus >= 202302L
    #define MGL_MAYBE_CONSTEXPR constexpr
#else
    #define MGL_MAYBE_CONSTEXPR
#endif

この MGL_MAYBE_CONSTEXPR マクロは、C++23以降でコンパイルした場合はその内容を constexpr に、そうでない場合は無に置き換えます。

この導入に伴い、一部のAPIの constexpr が MGL_MAYBE_CONSTEXPR に置き換えられます。これはアプリケーション側に影響を及ぼす破壊的変更となりますが、それを回避するための対策も考えており、それについては後述します。

MGL::STL以下のコンテナのハッシュ関数が定義されない問題

ちょっと上級者向けの内容。これはコンパイラの差異ではなく、標準ライブラリの実装による差異です。

gcc(g++)で標準的に用いられるライブラリは libstdc++ と呼ばれるものが使用されています。これを用いた場合、次のようなプログラムを書いた場合にコンパイルエラーとなります。

MGL::STL::unordered_map<MGL::STL::string, int> map;

auto it = map.find("find_key");

MGL::STLの名前空間で定義されているクラスは、MGLのアロケータを使用するように指定してある同名の標準ライブラリのクラスです。あくまで使用するアロケータが異なるだけで、実装そのものは標準ライブラリがそのまま使用されます。

詳細はこちら。

さて、上記のプログラムが何故通らないのか。その理由は MGL::STL::string がハッシュ計算の定義を持たないためです。

unordered_map(キーの重複を許容しない連想配列)などの一部のコンテナは、キーを比較するためにハッシュ関数を要します。上記の find() などはその一例で、この関数を使用するにはキーに指定した型のハッシュ値を算出するためのクラスを定義する必要があります。

MGL::STL以下で定義しているエイリアスは次の通りです。

//! std::basic_stringの代替
template <class charT, class traits = std::char_traits<charT>>
using basic_string = std::basic_string<charT, traits, Allocator<charT>>;

//! std::stringの代替
using string = basic_string<char>;


//! std::unordered_mapの代替
template <class Key, class T, class Hash = std::hash<Key>, class Pred = std::equal_to<Key>>
using unordered_map = std::unordered_map<Key, T, Hash, Pred, Allocator<std::pair<const Key, T>>>;

unordered_map のテンプレートの第3引数がハッシュの算出を定義したクラスの指定です。std::hash は標準ライブラリが備えているハッシュ算出用のテンプレートクラスで、基本的な型はこの指定で賄えるようになっています。ハッシュ関数を省略した場合はこの std::hash が用いられるため、数値や文字列といった基本的な型をキーに用いる限りはあまり意識する必要がない仕組みになっています。

さて、ここで標準ライブラリの実装による差異が生じていました。

上記のようにアロケータを変更した basic_string 型を定義した場合、clangで用いられる libc++ やMSVCで用いられるMicrosoft製のSTLはそれに応じたハッシュ関数が定義されます。

一方、libstdc++ はアロケータを変更した場合のハッシュ関数は定義されません。故に、上記のプログラムは適格とならずエラーを出力していました。

何故このような差異が生じているのかまでは不明ですが、恐らくそこまで細かい要件が定められていないのでしょう。現実問題として、少なくとも3者の標準ライブラリのソースコードをそれぞれ確認した限りでは上記のような実装になっていました。

理由は何であれ、手っ取り早く解決するなら次のようになります。

struct Hash
{
    size_t operator()(std::string_view view) const
    {
        return std::hash<std::string_view>{}(view);
    }
};


MGL::STL::unordered_map<MGL::STL::string, int, Hash> map;

auto it = map.find("find_key");

ハッシュ計算のための定義はどこかに用意できるとしても、これを使用する際に毎回指定するのは面倒ですね。上手くテンプレートを酷使すれば何とかなるかもしれませんが、そっちはそっちで闇の深い領域になってしまいます。

とりえあず、MGL内部で問題が発生している箇所には上記のような対応策を一旦適用します。アプリケーション側から使用する際に面倒なのは既知の問題として挙げておきましょう。

その他の細かい問題

それ以外で問題が生じていた箇所は、主にヘッダの依存関係に関わる部分でした。これは極力きちんとインクルードするのがお作法と言えるのですが、実際問題として見落としがちなのですよね。

あと、昔から当たり前のように使用していたので勘違いしていましたが、数学関数の末尾に'f'が付く( sinf() や cosf() など)ってC++の標準ライブラリの仕様には含まれていないみたいですね。これは前回の更新の際に気付いてある程度修正していたのですが、一部残ったままになっていたようです。


C++23への移行調査

結論から言うと、今回は見送ることにしました。ただし、コンパイルオプションでC++23を指定した場合でも問題なくコンパイルできるようにはしておきます。

C++23に移行したかった理由の1つはモジュール関連だったのですが、これがなかなかに複雑でして、ソースコードだけでなくビルド環境そのものを整え直す必要がありそうでした。これには結構な時間が掛かりそうなのに加えて、今の私の理解度では上手く移行できる自信もありません。

あと、これは頭から抜けていたのですが、ツール類は Homebrew からインストールする場合に各々の環境でコンパイルする仕組みになっています。すなわち、それらを導入するために最新のコンパイラを入れる必要があり、このハードルはかなり高めです。私の手元の環境でも、先日導入したLinux(参考:MGL週報 #68 - Pop!_OSお試しインストール)や古いMacBookでは少し古めのコンパイラが標準であるため、こういった部分でも問題が生じてしまいます。

一方で、アプリケーション側でC++23を使用したい場合も想定されますので、MGLそのものをC++23でコンパイル可能なように対応しておきます。この場合、先述の MGL_MAYBE_CONSTEXPR がちゃんと constexpr となりますので、この方法で破壊的変更の影響も回避できるという寸法です。

ちなみに、参考にした資料はこちら。

やはり公式がきちんとドキュメント化してくれているのは強いですね。MGLも見習わないと。


その他

せっかく購入したIllustratorでアウトラインフォントを作りたいと考えていましたが、TrueTypeやOpenTypeにエクスポートする機能までは備えていないみたいですね。社外製のプラグインで対応可能みたいですが、和文フォントの出力には少々問題があるとの事でした。

となると、フォントの作成に関してはやはりGlyphsあたりが最適でしょうか。という訳で、とりあえずこれを注文しておきました。

いいなと思ったら応援しよう!