C++20移行予定者のための旧C++ガイド
C++20移行予定者のための旧C++ガイド
この記事はC++ Advent Calendar 2020の20日目の記事です.書いてるのは21~23日ですが…(遅刻しました.申し訳…)
Iです.20日なのでC++20の話をします.
先日C++20が無事(なんとか2020年内に)publishされました :tada:
みなさんC++20使ってますか?私は…まだあんまり使えてないです…
私がメインで使っているのはC++17まじりのC++14みたいな感じ(C++17の機能を部分的に知らず,使えていない状態)で,まぁ世の中結構そんな感じの人は多いのかなと思っています.
で,今開発してるコードが古いC++ベースだとして,今後それらの資産をC++20にそのまま移行できるか?というと,これが思ったより大変かもしれません.
C++20は大規模な変更点が多く,正直標準C++史上では過去類を見ないレベルで断絶を伴うな,と感じます.
この辺りのお気持ちは21日目の記事に認めるとして,今日は「C++20と親和性の高い旧C++によるコードの書き方」,言い換えれば「いかにしてC++20に少ない変更で移行できる旧C++コードを書くか」について話します.
あ, 有識者各位からのマサカリを大募集しておりますので各位何卒よろしくお願いいたします!!!!!!!!!
モジュールの使用を諦める
これは旧C++コードに限らず,C++20ユーザーであっても モジュールの実運用は暫くの間諦めましょう .
とだけ書くのはいかがかと思うのでもう少しちゃんと書くと,特定コンパイラのみに依存しないコードを書きたい場合,今の所モジュールを正しく使うのは非常に厳しい状況にあります.
理由は2つあって,
- そもそも現状各コンパイラのモジュール実装が完全でない
- GCCは
devel/c++-modules
ブランチで鋭意開発中 - Clangはまだモジュールパーティションなど一部機能が未実装
- MSVCは基本的な部分は実装済みで,残りはP1779R3,P1857R3,P2115R0,P1815R2のみっぽい?
- 処理系ごとに実装がまちまちすぎてCMakeなどの(メタ)ビルドシステムが対応しきるのも暫く時間かかりそう
- そもそも依存関係の解決をどう図るのか(ビルドシステムのレイヤーで誰かしらがC++コードをパースする必要がある)などの問題もあり… → これについてはP1857R3の導入により解決するっぽい それはそれとしてまた壊れる既存のコードが増えますね…
- GCCは
- モジュールの規格自体がかなり怪しい
- 「処理系に任せるわ」があまりにも多すぎて,実際コンパイラごとに異なる挙動を示す部分がある
- 正直そのうちdefect report・遡及修正出るんじゃねぇかなぁと思ってますがどうだろ…
これらに加えて,可視・到達可能などの概念が新規に導入される関係で旧C++コードからC++20に移行するとなると新たにモジュールを導入するのはそれなりに変更量が多くなることが見込まれ,C++20プロジェクトかつ最初からモジュールを運用することを念頭に置いている場合でない限り,使用は容易ではないと考えています.
ですので,現時点で現実的にモジュールが使えるのは特定コンパイラのみを使うC++20プロジェクトか人柱くらいで,緩やかにC++20に移行していく予定の方は端からモジュールの使用は諦めておいたほうが良いかなと個人的には思います.
もちろん,モジュールがある程度枯れてきた頃に一念発起して頑張って移行するのはそれはそれで良いのですが,今のうちにそれを念頭に置いた動きをする必要は必ずしもないかなぁという感じ.
u8
文字列周りを使用しない
C++20における破壊的変更として,従来 char
型の文字列として扱われていた u8
プレフィックス文字列リテラル及び u8
文字列系関数が char8_t
型の文字列に変更になりました.
したがって,C++17までで u8
文字列に関する何かを使ってるコードは大抵C++20でコンパイルが通らなくなります.
というわけで, u8
文字列周りはC++20に移行するまでは触らないようにしたほうが良いでしょう.
標準化委員会がもっと早く導入していればこんなことには…
メンバ関数内のラムダ式について,メンバのキャプチャは明示的に初期化キャプチャする
C++17までは this
が デフォルトコピーキャプチャ([=]
)でキャプチャされるため, [=, this]
のような表記は 指定が重複していることになり 禁止されていました.
これは,デフォルトでコピーされるということはそうでない参照キャプチャのみを明記すべき,ということで, this
ポインタのキャプチャはコピーキャプチャなのでデフォルトの指定に対して重複する余分な記述と見做されていたからです(ちなみに,参照キャプチャについて考えていくと, this
はprvalueなので参照キャプチャはできず, [&, this]
は指定が重複していないので問題がありません.また,デフォルト参照キャプチャ([&]
)下では何故か this
ポインタの暗黙のコピーキャプチャが行われます.どうして…).
しかし,C++20からは [=, this]
表記が認められるようになり, さらに [=]
による this
の暗黙コピーキャプチャは非推奨になりました .
一応C++20時点では [=]
による this
の暗黙キャプチャは非推奨ではあるものの動きはします.
とはいえ,非推奨となることを知っていながら非推奨になるバージョンに移行する予定のコードで使うことはないと思いますので, this
の暗黙キャプチャは避けることとしましょう.
というわけで具体的な方策なのですが,そもそもとして以下の2つの前提があります.
-
this
のコピーキャプチャを使うとメンバには参照アクセスをすることになる- ポインタを経由したアクセスになるため
- メンバの値キャプチャがしたい場合は
this
のコピーキャプチャは使ってはいけない
- メンバを直接キャプチャすることはできない
- 直接キャプチャできるのは基本的にローカル変数のみ
- 初期化キャプチャであればメンバでも取れる
以上から,初期化キャプチャを用いて値/参照キャプチャを明示しつつ必要なものだけキャプチャするのが確実でしょう.
ところで,C++11では初期化キャプチャは使えません.どうすればよいかというと,以下のようにローカル変数として参照を作ってそれをキャプチャします.
#include<functional>
struct S{
int a;
float b;
std::function<void(void)> f(){
int& a = this->a;
float& b = this->b;
return [a, &b]{
a; //int
b; //float&
};
}
};
一応メンバの参照キャプチャしか使わない場合は this
のみをコピーキャプチャするのは手ですが,他の箇所と記述を統一したほうが混乱を招かずに済みそうです.
また,より保守的な戦略として任意のラムダ式でデフォルトキャプチャを使わない(任意のキャプチャを(時には初期化キャプチャを用いて)明示的に行う)というものもあります.
iterator周りの変更に注意する
C++20でRangeが導入されたことに伴い,iterator周りに大規模な変更が入っています.
詳細はonihusubeさんの11日目の記事,14日目の記事を参照していただくとして,他にもアクセスに使うのがCPOに変わるとか,結構差分は大きい印象です.
また,C++17まではなんちゃってiterator(iteratorの要件を満たさないがrange-based forや使用するiterator関数で要求される範囲の操作はできる型)でなんとかなることもありましたが,C++20以降ではその辺りをコンセプトでチェックするようになっているのでなんちゃってiteratorだとうまく動作しないということも多々あります.
将来的にC++20に移行する予定があるなら,C++17 iteratorとして問題ないコードくらいにはしておく必要がありそうです(いやまぁ,そもそもなんちゃってiteratorを運用すること自体問題なんですが…でもiteratorにデフォルトコンストラクタ用意したくないことありませんか?私はある…).
その他細かい非推奨化・削除に注意する
C++20で非推奨化・削除された機能の中には正直そんなの使ってたやつおるんか?みたいな機能も多々ありますが,一応以下は気にしておいて良さそうです.
-
std::is_pod
: C++20で非推奨化 -
std::aligned_storage
,std::aligned_union
: C++23で非推奨化 -
std::allocator
のメンバや特殊化 : C++20で削除 -
std::is_literal_type
: C++20で削除 -
std::result_of
: C++20で削除
まとめ
…あれ?いざ書いてみたらあんま気をつけることなかったな…とはいえ,C++20は既存のコードはそのままに手放しに新機能を使えるわけではなく,使っている機能によっては様々な部分で対応コストがかかりそうなことはご理解いただけたかと思います.
まだC++20は使わないけど今後使うつもり,という方は,上記のような部分に注意しつつコードを書くと良いのではないでしょうか.
21日(の枠)は私です.遅刻です.ゆるして…
Discussion