molecular coordinates

/var/log/coord_e.log

C++17と標準ライブラリ: C++17を使うときに気をつけたいこと

この記事では、現在(2019/1/19)新規プロジェクトでC++17を気軽に使い始めて困らないかということを、主に標準ライブラリに焦点を当てて考察する。

C++17は2017年に標準規格化されたC++バージョンだ。C++14、C++11と比べるととても使いやすくなっているので自分も使っていきたいと思うし、広く普及して欲しいと思っている。

しかし結論から言うと、まだC++17(の標準ライブラリ)は軽い気持ちで使い始められるものではないように感じた。

C++17に対応した標準ライブラリは、多くの安定版ディストリビューションでまだ使用可能ではないからだ。

※頑張って正確性に気を使っていますが、事実でない部分などありましたら教えてください。申し訳ない。

C++を構成する要素

一口にC++と言ってもそう単純ではなくて、以下の3つの要素の組み合わせを私たちは普段"C++"と呼んでいる。

3つ要素があるということはそれぞれにバージョンがあるわけで、C++17だったら"C++17のプリプロセッサ"、"C++17のコア言語"、"C++17の標準ライブラリ"がある。

例えば

  • __has_includeディレクティブ: "C++17で入ったプリプロセッサの機能"
  • 構造化束縛: "C++17で入ったコア言語機能"
  • <filesystem>: "C++17で入った標準ライブラリ"

となる。

さて、今回問題になるのはこの3つのうちの標準ライブラリである。(前者2つはコンパイラが対応していればいい、という単純な話なので)

C++の標準ライブラリ

これはコンパイラとは独立したもので*1、様々な実装が出回っている。

ここでは代表的なものとして以下の2つを題材にする*2:

  • libstdc++: gccと一緒に開発されている標準ライブラリ。普通はこれが入っている
  • libc++: LLVMが作っている標準ライブラリ。規格準拠が早いようだ

C++17の代表的な新機能について、それぞれの実装がC++17に対応したバージョンを以下に示す:

  <variant> <any> <optional> <filesystem> <string_view> Parallelism TS <type_traits>_v
libstdc++ 7.1 7.1 7.1 8.1 7.1 No 7.1
libc++ 4.0 4.0※ 4.0※ 7.0 4.0※ No 7.0

公式ページではP0220R1はIn progressになっている(2019/1/19)が、どうやら4.0のあたりでこの3つは入っているらしい

なおこれは2019/1/19時点の情報であり、最新の情報は以下の一次ソースで確認して欲しい:

一般的な環境

次に一般的に使われている環境*3で、どのバージョンの標準ライブラリが使える*4かを以下に示す。

  Ubuntu Bionic Debian Stretch Fedora 29 CentOS 7
libstdc++ 6.4 6.3 8.2 4.8.5
libc++ 6.0 3.5 7.0 No

と、いうことで、C++17標準ライブラリを使用したソースは、ほとんどの環境でコンパイルできないのである。

LLVM公式のリポジトリを使えばUbuntu/Debianでは最新のlibc++が手に入るので*5、これを使えば救いがあるように見える。

しかし、libc++コンパイルされたオブジェクトには、libstdc++コンパイルされたライブラリをリンクできない。一般的にバイナリで配布されているライブラリはlibstdc++でコンパイルされているため*6、libc++でうまくいくと有頂天になっている矢先、下のようなエラーで詰む。

../lib/libflom.so: undefined reference to `google::protobuf::util::MessageToJsonString(google::protobuf::Message const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, google::protobuf::util::JsonPrintOptions const&)'
../lib/libflom.so: undefined reference to `google::protobuf::Message::ParseFromIstream(std::__1::basic_istream<char, std::__1::char_traits<char> >*)'
../lib/libflom.so: undefined reference to `google::protobuf::util::MessageToJsonString(google::protobuf::Message const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >*, google::protobuf::util::JsonPrintOptions const&)'

これはlibstdc++でコンパイルされたprotobufを、libc++でコンパイルされたライブラリにリンクしようとした時に出たエラーの一部です。きつい。*7

バイナリ配布なら問題ないのでは?

「ユーザーがビルドする」というシチュエーションがないと断言できる場合。

これは問題なさそうだが、<filesystem>などは実行時にlibstdc++/libc++の共有ライブラリが必要なのでやはり慎重にならなければならない。

あんまり調べてないけど、他にも実行時に共有ライブラリが必要な機能はあると思う。

結論

C++17(の標準ライブラリ)を使い始めるときはビルドする環境や外部ライブラリをリンクするかなどをよく考えて、慎重になった方が良さそう

特に<optional>なんかは手軽さから一度使い始めるとかなり依存してしまいがち(私は)なので、突然「ラズパイでビルドするぞ!」などとなった時やXenialまでしか使えないCIなどで詰む

おまけ

主要ディストリのC++17標準ライブラリ対応状況(コンパイル可能: Y, コンパイル不可能: N)

libstdc++ Ubuntu Bionic Debian Stretch Fedora 29 CentOS 7
<variant> N N Y N
<any> N N Y N
<optional> N N Y N
<filesystem> N N Y N
<string_view> N N Y N
Parallelism TS N N N N
<type_traits>_v N N Y N
libc++ Ubuntu Bionic Debian Stretch Fedora 29 CentOS 7
<variant> Y N※ Y N
<any> Y N※ Y N
<optional> Y N※ Y N
<filesystem> N※ N※ Y N
<string_view> Y N※ Y N
Parallelism TS N N N N
<type_traits>_v Y N※ Y N

https://apt.llvm.org/から最新のlibc++を入手すれば使えるようになるでしょう(それはそう)

Fedora最強かよ。

 

2019/1/19更新: タイトルと冒頭をより意図が伝わるように修正、libc++の表を一部修正

 

*1:なのでgccでlibc++を使うだとか、clangでlibstdc++を使うことももちろんできる

*2: これ以外だとiccやMSVCにくっついてるものがそれぞれあると思う(名前を知らない)

*3:ディストリの選出は適当です

*4:ビルドすることなく標準のパッケージマネージャーでインストールできる

*5:親切にもLLVMはJessie/Trustyにまでリポジトリを用意してくれている

*6:要出典

*7:何がきついって、初見ではこれprotobuf側で何かシンボルが抜けていると思うじゃん...