C++のCovarient Return Typesを検証してみた
C++でvirtualの関数を継承(Override)したりして関数を書き直すことになると、普通はリターンタイプを同じにして返すのが普通ですね。 しかし継承したタイプのOverrideした関数のリターン値がベース仮想関数の返す型を継承としたものとならば、そのままに使うには外部から再度タイプキャストをしなければならないため、ややめんどいです。
Convarient Return Typesイディオムは、Overrideした仮想関数のリターンタイプは元となる仮想関数のタイプを継承したタイプどれも容認することを使用したテクニックとなります。これで継承しているタイプに併せてまたキャスティングする必要がなくなります。
早速コードを見ますね。
#include <type_traits> struct A { static constexpr int value = 13; }; struct B : public A { static constexpr int value = 19; }; struct C : public B { static constexpr int value = 137; }; struct D final { static constexpr int value = 251; }; struct AVirtual { virtual ~AVirtual() = default; virtual A* Get() { return this->pInstance; }; private: A* pInstance = nullptr; }; struct BVirtual : public AVirtual { virtual ~BVirtual() = default; virtual B* Get() override { return this->pInstance; } private: B* pInstance = nullptr; }; struct CVirtual : public BVirtual { virtual ~CVirtual() = default; virtual C* Get() override final { return this->pInstance; } private: C* pInstance = nullptr; }; /* struct DError : public BVirtual { virtual ~DError() = default; virtual D* Get() override final { return this->pInstance; } private: D* pInstance = nullptr; }*/ int main() { BVirtual* b = new BVirtual(); auto* pB = b->Get(); AVirtual* a = new BVirtual(); auto* pA = a->Get(); static_assert(std::remove_pointer_t<decltype(pB)>::value == B::value); static_assert(std::remove_pointer_t<decltype(pA)>::value == A::value); delete b; }
このイディオムの問題は、変数のコンパイル時タイプによって仮想関数のリターンタイプをどれにするかを決定することです。これは仮想関数の引数のデフォルト値を決めるときに、変数のコンパイル時タイプを見てから決めることと一致してますよね。でもデフォルト値よりはリターンタイプによってCovarientにタイプが違うようにするのは良い方法だと思います。auto
を使っても、インテリセンスでどのタイプかはっきりと伝えてくれるし、デフォルト値みたいに暗示的に適用させるものではないからです。
ちなみに継承してないタイプを入れようとするとこうなります。
<source>:32:16: error: return type of virtual function 'Get' is not covariant with the return type of the function it overrides ('D *' is not derived from 'B *') virtual D* Get() override final { return this->pInstance; } ~~ ^
Covariantでないと言われてエラーが出ます。リターンタイプが継承しているかないかをコンパイル時に判断してくれますから良いですね。