C++クイズ: Workarounds

公開:2020/12/08
更新:2020/12/26
6 min読了の目安(約5700字TECH技術記事

C++クイズ: Workarounds

この記事はC++ Advent Calendar 2020の9日目の記事です.

Iです.C++ Advent Calendar,枠はまだまだ余っておりますので皆さん参加の方お願いします…


「だれがよんだか~ C++マスター~
 だれがよんだか~ C++マスター~
 いってみよ~! やってみよ~!
 さーぁ さぁさぁ
 (チャッチャー)クイズアワー~~」

本日の記事は「こういうシチュエーションのとき,どのようにWorkaroundを実装すればよいでしょうか?」というクイズです.問題文と,文末にリポジトリを用意しています.想定回答は 今年中に公開予定です公開しました.それではどうぞ.

問題文

あなたは super_awesome_library_that_has_breaking_changes_frequently というライブラリを使用してとあるソフトウェアを開発している.
super_awesome_library_that_has_breaking_changes_frequently は便利なライブラリだが,バージョンアップの度にインターフェースに破壊的変更が加わるライブラリである.
その上, super_awesome_library_that_has_breaking_changes_frequently は色んな人が勝手にforkを生やしまくっており,数値上同じバージョンでも実際には中身が異なることもあり,バージョン番号を表すマクロ SUPER_AWESOME_LIBRARY_THAT_HAS_BREAKING_CHANGES_FREQUENTLY_VERSION は実質的に機能していない.

さて,あなたはこの度 super_awesome_library_that_has_breaking_changes_frequently を更新することになった.
今回も破壊的変更が複数入っている.
しかし,一般的にシステムにインストールされる super_awesome_library_that_has_breaking_changes_frequently を1バージョンしかサポートしないのも良くないと考えたあなたは,1つ前の super_awesome_library_that_has_breaking_changes_frequently もサポートすることにした.
当然そのままではインターフェースが異なるためコードが2つに増えてしまい,保守コストが倍になる.
これを避けるため,あなたは super_awesome_library_that_has_breaking_changes_frequently のラッパーライブラリである sal_wrapper を開発することにした.

以下の5つのインターフェースを実装せよ.

  1. constexpr int ::sal_wrapper::a(const ::sal::awesome_type&, ::std::size_t)
  2. contexpr const int* ::sal_wrapper::b(const ::sal::awesome_type&, ::std::size_t)
  3. contexpr int ::sal_wrapper::c(auto&&...)
  4. virtual void ::sal_wrapper::excellent_base::d(int)
  5. auto ::sal_wrapper::hypers(const ::sal::excellent_type&)

1. a : 関数名の変更

super_awesome_library_that_has_breaking_changes_frequentlyawesome_type という型を持ち,バージョン1では a という std::size_t 型の値を受け取り int 型の値を返すconstメンバ関数を持っている.
しかし,バージョン2ではこのメンバ関数の名前が a2 に変わってしまった.
そこで, awesome_type のconst lvalue-refと std::size_t 型の値を受け取り,適切なメンバ関数を呼ぶことで int 型の値を返す関数 sal_wrapper::a を定義したい.
この際, awesome_type::a または awesome_type::a2constexpr な関数であるため, sal_wrapper::aconstexpr な関数とすること.

2. b : 戻り値型の変更

バージョン1の awesome_typestd::size_t 型の値を受け取って const int* 型の値を返すconstメンバ関数 b も持っている.
しかし,バージョン2では const int* ではなく std::optional<const int*> 型の値を返すように変更された.
バージョン1では引数によっては nullptr が返っていたようだが,バージョン2ではこうした際 nullptr の代わりに std::nullopt を返すようになったようである.
そこで, awesome_type のconst lvalue-refと std::size_t 型の値を受け取り,メンバ関数 awesome_type::b を呼ぶことで const int* 型の値を返す関数 sal_wrapper::b を定義したい.
a と同様 constexpr な関数とすること.

3. c : 関数定義位置の変更

バージョン1の super_awesome_library_that_has_breaking_changes_frequently には super_awesome_library_that_has_breaking_changes_frequently::c という,任意の型の引数を任意個受け取り int 型の値を返す関数がある.
バージョン2では,これが awesome_type の静的メンバ関数になった.
そこで,任意の型の引数を任意個受け取り適切な関数を呼ぶことで int 型の値を返す関数 sal_wrapper::c を定義したい.
a と同様 constexpr な関数とすること.

4. d : 仮想関数の引数の変更

super_awesome_library_that_has_breaking_changes_frequently には excellent_type という抽象クラスもある.
バージョン1の excellent_typevirtual void d(int) = 0; という純粋仮想関数を持つ.
一方,バージョン2の excellent_typevirtual void d(int, super_awesome_library_that_has_breaking_changes_frequently::super_flags) = 0; と,引数が1つ増えている.
ここで, super_awesome_library_that_has_breaking_changes_frequently::super_flags はscoped enumである.
バージョン1の x.d(y) という呼び出しは,バージョン2の x.d(y, super_flags::super) に相当する.
また,現状のソフトウェアにおいて第2引数が super_flags::super 以外で呼び出されることは無く,このフラグの内容は気にしなくて良いものとする.
このとき,バージョン1の d と同じインターフェースの純粋仮想関数を持つような型 sal_wrapper::excellent_base を定義したい.
これを用いて,以下のように記述することを想定している.

namespace sal = super_awesome_library_that_has_breaking_changes_frequently;
struct T_ver1 : sal::excellent_type{
  void d(int)override{/*implementation*/} // こちらはバージョン1でしか動かない
};
struct T : sal_wrapper::excellent_base{
  void d(int)override{/*implementaion*/} //こちらはバージョン2でも動く
};

super_awesome_library_that_has_breaking_changes_frequently が定義しているのは excellent_type だが, sal_wrapper で定義するのは excellent_base であることに注意せよ.

5. hypers : range対応

バージョン1の excellent_type には std::size_t を返す無引数constメンバ関数 hyper_size と, std::size_t を受け取って std::string のconst lvalue-refを返す access_hyper_data constメンバ関数が存在し,これを用いて excellent_type の持つデータにインデックスアクセスを行うことができる.
一方,バージョン2の excellent_type にはイテレータでアクセス可能なrangeを返す hypers constメンバ関数が存在する.
また, hypers の要素の型 hyper_data には内部の std::string のconst lvalue-refを得るための access constメンバ関数が存在する.
したがって,仮に複数バージョンが共存し得た場合,内部に同じデータを持つバージョン1の excellent_type の変数 ver_1 とバージョン2の excellent_type の変数 ver_2 を用いた以下の式の結果が true となる.

ver_1.access_hyper_data(0) == ver_2.hypers().begin()->access()

このとき, excellent_type のconst lvalue-refを受け取って適切なrangeを返す sal_wrapper::hypers 関数を定義したい.
この関数で返るrangeは引数で渡した excellent_type の値より長く生存したときの挙動については規定しない.
また,このrangeはバージョン2の excellent_type の値 x に対して, x.hypers() と同様に扱うことができるものとする.
すなわち,以下の式の結果が true となる.

ver_1.access_hyper_data(0) == sal_wrapper::hypers(ver_1).begin()->access() &&
ver_2.hypers().begin()->access() == sal_wrapper::hypers(ver_2).begin()->access()

制約

  • 関数を実装する際に SUPER_AWESOME_LIBRARY_THAT_HAS_BREAKING_CHANGES_FREQUENTLY_VERSION を参照してはいけない
  • super_awesome_library_that_has_breaking_changes_frequently 名前空間内は汚染して良いものとする.
    • 任意の宣言・定義・特殊化を super_awesome_library_that_has_breaking_changes_frequently 名前空間内で行って良い.
  • 実装は sal_wrapper.hpp 内で行う.それ以外のファイルを変更してはいけない.
  • C++17以上を用いる.
    • 希望であればC++20を用いて良い.

リポジトリ

GitHub

上記リポジトリ内の sal_wrapper.hpp に実装を行い, make test が正常に実行されれば全ての問題に正解したことになる.
また,以下にWandboxのURLを載せる.オンラインで試したい方はどうぞ.

Wandbox


明日の枠は空いてるので誰か入ってください!!!!!!