パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
ソースコード(明示的実体化メタプログラミング)

関数テンプレート実装隠蔽する場合に多数の明示的実体化をメタプログラミングにより自動化する方法を試す。

本項目はどちらかと言えばメタプログラミングの演習目的で、実用はそれぞれの明示的実体化定義を愚直に書き連ねる方が可読性やメンテナンス性からも良いと思う。メタプログラミングは結局暗黙的実体化に依存せざるを得ないが、その方法がmingw-w64以外の実装にも一般的に適用できるかどうかも怪しい。

概要

本項目が提案するのはTMyInstantiateAllクラステンプレートで、仮引数パック(JTC1/SC22/WG21 N4659 17.5.3/p1)に受ける複数の型(型リスト)で明示的実体化して、任意の関数テンプレートを各型で暗黙的実体化する。関数テンプレートの実体化はヘルパーとなるクラステンプレート(例えばTMyInstantiateOneクラステンプレート)が行い、その仮引数に実体化する型を受ける。TMyInstantiateAllの最初の仮引数はテンプレートテンプレートとしてヘルパークラステンプレートを受け、二番目以降が仮引数パックで型リストを受ける。TMyInstantiateAllは共用するクラステンプレートとしてインクルードファイル(TMyInstanciateAll.h)で供給し、クライアントはこれをインクルードして自らが作成するヘルパークラステンプレートを最初の実引数に与える。

// X.h (g(T) explicit instantiation declarations are not required)
void f(int);
template<typename T> void g(T);
// X.cpp (g(T) explicit instantiations for all possible types by TMyInstantiateAll)
#include "X.h"
#include "TMyInstanciateAll.h"
#include <iostream>
void f(int val) {std::cout<<"f(X.cpp):Given value is "<<val<<std::endl;}
template<typename T> void g(T val) {std::cout<<"g(X.cpp):Given value is "<<val<<std::endl;}
namespace
{
template<typename T> struct TMyInstantiateOne
{
void (*funcPtr_)(T);
TMyInstantiateOne():funcPtr_{&g<T>} {}
};
}
template struct TMyInstantiateAll<TMyInstantiateOne,int,double>;

コンパイラは以下に各テンプレートを実体化する。

  1. TMyInstantiateAll<TMyInstantiateOne,int,double>を明示的実体化する
  2. TMyInstantiateOne<int>、TMyInstantiateOne<double>を暗黙的実体化する
  3. g<int>、g<double>を暗黙的実体化する

TMyInstantiateAll<...>にTMyInstantiateOne<T>を暗黙的実体化させるため後者は前者のサブオブジェクトとするが、具体的な方法は次項で詳述する。TMyInstantiateOne<T>にg<T>を暗黙的実体化させるため、そのポインタを取得してメンバ変数に格納する。少なくともmingw-w64では関数テンプレート特殊化のポインタを取得して何らかの変数に代入すればその暗黙的実体化を得ることができる。このように暗黙的実体化に依存するのは明示的実体化を"カスケード"できないためであるが、こういった手法がmingw-w64以外にも一般に使えるかどうか甚だ自信が無い。

TMyInstantiateAllクラステンプレート

TMyInstantiateAllは4種類の実装方法を試した。ヘルパークラステンプレートを仮引数InstantiateOneに受け、InstantiateOne<T>をサブオブジェクト(基底クラスあるいはメンバ変数の基底クラス)とする事で暗黙的実体化する。型リストで型が重複する場合は例外を除きコンパイラは警告する。InstantiateOne<T>を仮想基底とすれば回避できるが、コンストラクタに実引数を渡す場合に初期化順に起因する問題が発生する。例外は仮引数パック展開による実装で、型リストの全てを直接の基底とするため重複はコンパイルエラーとなる。

デフォルトコンストラクタは本体空であるが全ての実装で定義を省略できない。これを省略するとターゲットの関数テンプレート特殊化が実体化せずリンクエラーとなるが、コンパイラによるデフォルトコンストラクタ生成がテンプレート実体化より後になるためと考えられる。

TMyInstantiateAll.h(仮引数パック再帰)

テンプレート仮引数パックの再帰で実装する。

#ifndef TMYINSTANTIATEALL_H_INCLUDED
#define TMYINSTANTIATEALL_H_INCLUDED
template<template<typename> class,typename...> struct TMyInstantiateAll;
template<template<typename> class InstantiateOne,typename Head,typename... Tail>
struct TMyInstantiateAll<InstantiateOne,Head,Tail...>
:TMyInstantiateAll<InstantiateOne,Tail...>,InstantiateOne<Head>
{
TMyInstantiateAll() {} // We need to define the default constructor.
};
template<template<typename> class InstantiateOne> struct TMyInstantiateAll<InstantiateOne> {};
#endif // TMYINSTANTIATEALL_H_INCLUDED

TMyInstantiateAll.h(boostメタプログラミングシーケンス再帰)

boostメタプログラミングライブラリのシーケンス再帰で実装する。他実装とテンプレート仮引数を同じくするため再帰クラステンプレートとなるRecursionをメンバに内包し、その特殊化をrecursion_メンバ変数に持つ。

#ifndef TMYINSTANTIATEALL_H_INCLUDED
#define TMYINSTANTIATEALL_H_INCLUDED
#include <boost/mpl/list.hpp>
#include <boost/mpl/begin_end.hpp>
#include <boost/mpl/next_prior.hpp>
template<template<typename> class InstantiateOne,typename... List> struct TMyInstantiateAll
{
using Seq=typename boost::mpl::list<List...>::type;
template<typename B,typename E> struct Recursion
:Recursion<typename boost::mpl::next<B>::type,E>,InstantiateOne<typename boost::mpl::deref<B>::type>
{};
Recursion<typename boost::mpl::begin<Seq>::type,typename boost::mpl::end<Seq>::type> recursion_;
TMyInstantiateAll() {} // We need to define the default constructor.
};
template<template<typename> class InstantiateOne,typename... List> template<typename E>
struct TMyInstantiateAll<InstantiateOne,List...>::Recursion<E,E> {};
#endif // TMYINSTANTIATEALL_H_INCLUDED

TMyInstantiateAll.h(boostメタプログラミングfold)

boostメタプログラミングライブラリのfoldクラステンプレートで実装する。

#ifndef TMYINSTANTIATEALL_H_INCLUDED
#define TMYINSTANTIATEALL_H_INCLUDED
#include <boost/mpl/list.hpp>
#include <boost/mpl/fold.hpp>
#include <boost/mpl/empty_base.hpp>
#include <boost/mpl/inherit.hpp>
template<template<typename> class InstantiateOne,typename... List> struct TMyInstantiateAll
:boost::mpl::fold
<typename boost::mpl::list<List...>::type,boost::mpl::empty_base
,boost::mpl::inherit<boost::mpl::_1,InstantiateOne<boost::mpl::_2>>
>::type
{
TMyInstantiateAll() {} // We need to define the default constructor.
};
#endif // TMYINSTANTIATEALL_H_INCLUDED

TMyInstantiateAll.h(仮引数パック展開)

テンプレート仮引数パックの展開(N4659 17.5.3/p4)で実装する。型重複は複数の同一クラスが直接の基底クラスに現れエラーとなる。

#ifndef TMYINSTANTIATEALL_H_INCLUDED
#define TMYINSTANTIATEALL_H_INCLUDED
template<template<typename> class InstantiateOne,typename... List> struct TMyInstantiateAll
:InstantiateOne<List>...
{
TMyInstantiateAll() {} // We need to define the default constructor.
};
#endif // TMYINSTANTIATEALL_H_INCLUDED

テンプレートとpimplイディオム

テンプレートを考察するにおいて、疑似的なセパレートモデルでテンプレートをpimplイディオムに応用した。これをTMyInstantiateAllクラステンプレートで書き換える。

メンバ関数テンプレート

メンバ関数テンプレートは"関数を特殊化とするテンプレート化エンティティ"として暗黙的実体化する。ヘルパークラステンプレート(TMyInstanciateOne)は関数テンプレート特殊化に準じてメンバ関数のポインタをメンバ変数に保持する。ユーザー定義型はインクルードファイルなどで定義を供給する必要がある。

TMyTypeCheck1.h

#ifndef TMYTYPECHECK1_H
#define TMYTYPECHECK1_H
#include <memory>
class TMyTypeCheck1 final
{
private:
class Impl;
std::unique_ptr<Impl> pimpl_;
public:
TMyTypeCheck1();
~TMyTypeCheck1();
template<typename T> void Check(T val);
};
#endif // TMYTYPECHECK1_H

TMyTypeCheck1.cpp

#include "TMyTypeCheck1.h"
#include "TMyInstantiateAll.h"
#include <iostream>
#include <boost/core/demangle.hpp>
struct TMyTypeCheck1::Impl
{
template<typename T> void Check(T val)
{
std::cout<<"Given value is "<<val
<<" of which type is "<<boost::core::demangle(typeid(T).name())
<<"."<<std::endl;
}
};
TMyTypeCheck1::TMyTypeCheck1():pimpl_{new Impl} {}
TMyTypeCheck1::~TMyTypeCheck1() {}
template<typename T> void TMyTypeCheck1::Check(T val) {pimpl_->Check(val);}
namespace
{
template<typename T> struct TMyInstantiateOne
{
void(TMyTypeCheck1::*memFuncPtr_)(T);
TMyInstantiateOne():memFuncPtr_{&TMyTypeCheck1::Check<T>} {} // Instantiates the member function implicitly.
};
}
// TMyInstantiateAll explicit instantiation, which implicitly instantiates TMyTypeCheck1::Check for each type in the argument list.
template struct TMyInstantiateAll<TMyInstantiateOne,int,double,const char*>;

クラステンプレート

クラステンプレート明示的実体化は全メンバ関数の明示的実体化と同等と見なせるので(N4659 17.7.2/p8)、その全てを"関数を特殊化とするテンプレート化エンティティ"として暗黙的実体化させる。ヘルパークラステンプレート(TMyInstanciateOne)は関数テンプレート特殊化に準じて各メンバ関数のポインタをメンバ変数に保持する。ただしコンストラクタとデストラクタのメンバ関数ポインタは取得できず(15.1/p10、15.4/p2)、TMyInstantiateOneコンストラクタ本体でクラステンプレート特殊化のダミーインスタンスをローカルに定義して暗黙的実体化した。このダミーインスタンスをメンバに保持するとTMyInstantiateAllはデストラクタも明示的に定義する必要がある。なおユーザー定義型はインクルードファイルなどで定義を供給する必要がある。

TMyTypeCheck2.h

#ifndef TMYTYPECHECK2_H
#define TMYTYPECHECK2_H
#include <memory>
template<typename T> class TMyTypeCheck2 final
{
private:
class Impl;
std::unique_ptr<Impl> pimpl_;
public:
TMyTypeCheck2();
~TMyTypeCheck2();
void Check(T val);
};
#endif // TMYTYPECHECK2_H

TMyTypeCheck2.cpp

#include "TMyTypeCheck2.h"
#include "TMyInstantiateAll.h"
#include <iostream>
#include <boost/core/demangle.hpp>
template<typename T> struct TMyTypeCheck2<T>::Impl
{
void Check(T val)
{
std::cout<<"Given value is "<<val
<<" if it is interpreted as "<<boost::core::demangle(typeid(T).name())
<<"."<<std::endl;
}
};
template<typename T> TMyTypeCheck2<T>::TMyTypeCheck2():pimpl_{new Impl} {}
template<typename T> TMyTypeCheck2<T>::~TMyTypeCheck2() {}
template<typename T> void TMyTypeCheck2<T>::Check(T val) {pimpl_->Check(val);}
namespace
{
// Makes "dummy" local of the constructor. If it was a class member the explicit definition of ~TMyInstantiateAll() would be required.
template<typename T> struct TMyInstantiateOne
{
void(TMyTypeCheck2<T>::*memFuncPtr_)(T);
TMyInstantiateOne():memFuncPtr_{&TMyTypeCheck2<T>::Check} // Instantiates the member function implicitly.
{
TMyTypeCheck2<T> dummy; // Instantiates the constructor and the destructor implicitly.
}
};
}
// TMyInstantiateAll explicit instantiation, which implicitly instantiates TMyTypeCheck2 member functions for each type in the argument list.
template struct TMyInstantiateAll<TMyInstantiateOne,int,double,const char*>;