擬似仮想関数テンプレート

たまに仮想関数をテンプレート関数にしたくなることがあるが、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の勉強がてら作ってみたので記録として残しておく。

参考