C++クイズ: Workarounds
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つのインターフェースを実装せよ.
constexpr int ::sal_wrapper::a(const ::sal::awesome_type&, ::std::size_t)
contexpr const int* ::sal_wrapper::b(const ::sal::awesome_type&, ::std::size_t)
contexpr int ::sal_wrapper::c(auto&&...)
virtual void ::sal_wrapper::excellent_base::d(int)
auto ::sal_wrapper::hypers(const ::sal::excellent_type&)
a
: 関数名の変更
1. super_awesome_library_that_has_breaking_changes_frequently
は awesome_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::a2
は constexpr
な関数であるため, sal_wrapper::a
も constexpr
な関数とすること.
b
: 戻り値型の変更
2. バージョン1の awesome_type
は std::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
な関数とすること.
c
: 関数定義位置の変更
3. バージョン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
な関数とすること.
d
: 仮想関数の引数の変更
4. super_awesome_library_that_has_breaking_changes_frequently
には excellent_type
という抽象クラスもある.
バージョン1の excellent_type
は virtual void d(int) = 0;
という純粋仮想関数を持つ.
一方,バージョン2の excellent_type
は virtual 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
であることに注意せよ.
hypers
: range対応
5. バージョン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を用いて良い.
リポジトリ
上記リポジトリ内の sal_wrapper.hpp
に実装を行い, make test
が正常に実行されれば全ての問題に正解したことになる.
また,以下にWandboxのURLを載せる.オンラインで試したい方はどうぞ.
明日の枠は空いてるので誰か入ってください!!!!!!
Discussion