擬似仮想関数テンプレート
たまに仮想関数をテンプレート関数にしたくなることがあるが、C++ではできない。
#include <iostream> #include <typeinfo> namespace lib { class IDynamicType { public: virtual ~IDynamicType() = default; template<typename StaticType> virtual void function(const StaticType &value) = 0; // NG: テンプレート関数は仮想にできない }; } // namespace lib namespace app { class DynamicTypeA: public IDynamicType { public: template<typename StaticType> void function(const StaticType &value) override { // NG: テンプレート関数は仮想にできない std::cout << "DynamicTypeA::" << __func__ << "(" << typeid(value).name() << " " << value << ")\n"; } }; class DynamicTypeB: public IDynamicType { public: template<typename StaticType> void function(const StaticType &value) override { // NG: テンプレート関数は仮想にできない std::cout << "DynamicTypeB::" << __func__ << "(" << typeid(value).name() << " " << value << ")\n"; } }; } // namespace app
これは、非仮想のメンバーテンプレート関数と違って、新しい引数型を与えてfunction()が呼び出される度に、リンカーがIDynamicTypeクラスの仮想関数テーブルに対して新たなエントリーを追加しなければならなくなるからだ。
ただし、引数型を予め限定しても良いなら、クラステンプレートを使って擬似的に仮想関数テンプレートを作ることができる。
#include <algorithm> #include <cstddef> #include <memory> #include <string> namespace lib { template<typename... TPack> struct TypeList; template<> struct TypeList<> { static constexpr std::size_t size = 0; protected: ~TypeList() = default; }; template<typename THead, typename... TTail> struct TypeList<THead, TTail...>: TypeList<TTail...> { using head = THead; using tail = TypeList<TTail...>; static constexpr std::size_t size = sizeof...(TTail) + 1; protected: ~TypeList() = default; }; template<class TStaticTypeList, std::size_t t_size = TStaticTypeList::size> class DynamicType_IStaticType : public DynamicType_IStaticType<typename TStaticTypeList::tail> { protected: virtual ~DynamicType_IStaticType() = default; public: using DynamicType_IStaticType<typename TStaticTypeList::tail>::function; virtual void function(const typename TStaticTypeList::head &value) = 0; }; template<class TStaticTypeList> class DynamicType_IStaticType<TStaticTypeList, 1> { protected: virtual ~DynamicType_IStaticType() = default; public: virtual void function(const typename TStaticTypeList::head &value) = 0; }; template<class TStaticTypeList> class IDynamicType_IStaticTypeList : public DynamicType_IStaticType<TStaticTypeList> { }; using StaticTypeList = TypeList<int, std::string>; using IDynamicType = IDynamicType_IStaticTypeList<StaticTypeList>; class UseDynamicType { std::unique_ptr<IDynamicType> m_idynamic; public: explicit UseDynamicType(std::unique_ptr<IDynamicType> &&idynamic) : m_idynamic{std::move(idynamic)} {} public: void use_function() { m_idynamic->function(3); m_idynamic->function("xyz"); } }; } // namespace lib #include <iostream> #include <typeinfo> namespace app { template<class TStaticTypeList, std::size_t t_size = TStaticTypeList::size> class DynamicTypeA_StaticType : public DynamicTypeA_StaticType<typename TStaticTypeList::tail> { protected: virtual ~DynamicTypeA_StaticType() = default; public: using DynamicTypeA_StaticType<typename TStaticTypeList::tail>::function; void function(const typename TStaticTypeList::head &value) override { std::cout << "DynamicTypeA_StaticType::" << __func__ << "(" << typeid(value).name() << " " << value << ")\n"; } }; template<class TStaticTypeList> class DynamicTypeA_StaticType<TStaticTypeList, 0> : public lib::IDynamicType { protected: virtual ~DynamicTypeA_StaticType() = default; }; class DynamicTypeA: public DynamicTypeA_StaticType<lib::StaticTypeList> {}; template<class TStaticTypeList, std::size_t t_size = TStaticTypeList::size> class DynamicTypeB_StaticType : public DynamicTypeB_StaticType<typename TStaticTypeList::tail> { protected: virtual ~DynamicTypeB_StaticType() = default; public: using DynamicTypeB_StaticType<typename TStaticTypeList::tail>::function; void function(const typename TStaticTypeList::head &value) override { std::cout << "DynamicTypeB_StaticType::" << __func__ << "(" << typeid(value).name() << " " << value << ")\n"; } }; template<class TStaticTypeList> class DynamicTypeB_StaticType<TStaticTypeList, 0> : public lib::IDynamicType { protected: virtual ~DynamicTypeB_StaticType() = default; }; class DynamicTypeB: public DynamicTypeB_StaticType<lib::StaticTypeList> {}; } // namespace app
随分と複雑になってしまったが、仮想関数テンプレートであるかのように呼び出すことができる。
int main() { std::cout << "sizeof(lib::IDynamicType) = " << sizeof(lib::IDynamicType) << '\n'; { std::unique_ptr<app::DynamicTypeA> a{new app::DynamicTypeA}; std::cout << "sizeof(app::DynamicTypeA) = " << sizeof(app::DynamicTypeA) << '\n'; a->function(1); a->function("abc"); lib::UseDynamicType user{std::move(a)}; user.use_function(); } { std::unique_ptr<app::DynamicTypeB> b{new app::DynamicTypeB}; std::cout << "sizeof(app::DynamicTypeB) = " << sizeof(app::DynamicTypeB) << '\n'; b->function(2); b->function("def"); lib::UseDynamicType user{std::move(b)}; user.use_function(); } }
Clang 3.5.2/GCC 4.9.2でコンパイル~実行した結果は、以下のようになる。
sizeof(lib::IDynamicType) = 4 sizeof(app::DynamicTypeA) = 4 DynamicTypeA_StaticType::function(i 1) DynamicTypeA_StaticType::function(Ss abc) DynamicTypeA_StaticType::function(i 3) DynamicTypeA_StaticType::function(Ss xyz) sizeof(app::DynamicTypeB) = 4 DynamicTypeB_StaticType::function(i 2) DynamicTypeB_StaticType::function(Ss def) DynamicTypeB_StaticType::function(i 3) DynamicTypeB_StaticType::function(Ss xyz)
Visual C++ 2015 RCでコンパイル~実行した結果は、以下のようになる。
sizeof(lib::IDynamicType) = 4 sizeof(app::DynamicTypeA) = 4 DynamicTypeA_StaticType::function(int 1) DynamicTypeA_StaticType::function(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > abc) DynamicTypeA_StaticType::function(int 3) DynamicTypeA_StaticType::function(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > xyz) sizeof(app::DynamicTypeB) = 4 DynamicTypeB_StaticType::function(int 2) DynamicTypeB_StaticType::function(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > def) DynamicTypeB_StaticType::function(int 3) DynamicTypeB_StaticType::function(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > xyz)
クラス構成は、正確に表現できていないが以下の図のようになっている。
引数型が限定される上に、インターフェイス側も実装側も複雑なので、ここまでして実現したいことがあるかはわからないが、C++11の勉強がてら作ってみたので記録として残しておく。
参考
- 作者: ビャーネ・ストラウストラップ,Bjarne Stroustrup,柴田望洋
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2015/02/28
- メディア: 大型本
- この商品を含むブログ (8件) を見る
- 作者: επιστημη,高橋晶
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2014/04/17
- メディア: 単行本
- この商品を含むブログ (6件) を見る