【C++テンプレートメタプログラミング超入門】もう実行時処理トカゆるふわモテマクロは古い!? スマート女子の今年のクリスマスは C++テンプレートメタプログラミング でモテカワコンパイル時メイク♪


2014年クリスマスということで, C++テンプレートメタプログラミングについて書こうと思います!(((o(゚▽゚)o))) もちろん, 私は本当に初心者なので, 難しいことは全く分かりません>< だから, 私でも分かるレベルの, 初心者向け『テンプレートメタプログラミング超入門』ということで書いていきますφ(゚▽゚)oメモメモ

これは C++アドベントカレンダー2014 の25日目の記事です. (最終日に似合わずガチ初心者向けの内容です.)
前日はglm – グラフィックスプログラミングのためのC++数学系ライブラリー(by usagiさん)でした.

目次

  1. タイトルについて
  2. テンプレートとは
    1. テンプレート > 使用例
    2. おまけ: Cプリプロセッサのマクロでメタプロ

  3. コンパイル時計算とは
  4. メタプログラミング
  5. テンプレートメタプログラミング
    1. メタプロサンプルコード1: 足し合わせる
    2. メタプロサンプルコード2: 階乗
  6. もっとテンプレートメタプログラミング

タイトルについて


軽い気持ちでツイートしたのがなぜか1900RTを超えたので!


今回の内容はダイヤモンド継承は関係無いんですけど, C++の特徴的なことといったらダイヤモンド継承がひとつ挙げられると, 詳しい人が言っていたので, 無理やり入れようと思ったのです. (でもやっぱ関係無いから結局削りました.)

テンプレートとは

Wikipedia見るぞ〜(((o(゚▽゚)o)))

テンプレート (プログラミング)
(曖昧さ回避) この項目では、主にC++の言語仕様について説明しています。

プログラミングにおけるテンプレートは、静的型付けのC++でデータ型にとらわれずにコードを書くことを可能にする機能であり、C++においてはジェネリックプログラミングに用いられる。
C++においてテンプレートは多重継承や演算子多重定義と並ぶ重要な機能となった。STL (Standard Template Library)はテンプレートによって構築されたフレームワークとなっている。

引用元: テンプレート (プログラミング) | wikipedia
(((o(゚▽゚ ; )o))) …

(((o(゚▽゚ ; )o))) むずかしい

Wikipediaはいつも私には難しい><
きっと頭良い人はこういう説明でパパっと理解できちゃうんだろうなあ

ジェネリックプログラミング』っていうのは, データ型に依存しないプログラミングのやり方のこと…でいいのかな?
「このAdd関数はint型しか使えないよ〜ハイ残念」とかいうのを避けるみたいな! 「このAdd関数はintでもdoubleでも使えるよ〜」みたいな感じにするのを『ジェネリックプログラミング』っていうんだね!φ(゚▽゚)oメモメモ

で, テンプレートを使えばC++でジェネリックプログラミングできるってことなんですね! (プリプロセッサのマクロ使ってもできるけどあれはダメらしい)

それで, テンプレートには

  1. 関数テンプレート
  2. クラステンプレート
  3. メンバテンプレート
  4. エイリアステンプレート (C++11から)

の4つの種類があるらしいのですが, ここでは関数テンプレートのみを扱います.

使用例

たとえば, ふたつの数を足す関数 Add(x, y) を考えます!

まず, int型のやつです

auto Add(int x, int y) -> int
{
	return x + y;
}

int型のふたつの引数を受け取って, それらを足したintを返します.

次に, double型のやつ!
double型のふたつの引数を受け取って, それらを足したdoubleを返します.

auto Add(double x, double y) -> double
{
	return x + y;
}

/* どこかのC言語と違って(型や引数の数が違えば)関数の名前は被っても大丈夫です.(オーバーロード) (C言語のときは関数名 int_Add とか double_Add とかしなきゃいけなかった) */

表示させてみます.

#include <iostream>

auto Add(int x, int y) -> int
{
	return x + y;
}

auto Add(double x, double y) -> double
{
	return x + y;
}

auto main() -> int 
{
	std::cout << Add(2, 1) << std::endl; // 3
	std::cout << Add(2.5, 1.5) << std::endl; // 4
}

問題無く表示できる(((o(゚▽゚)o)))

でも, int版のAddも, double版のAddも, 中身同じですよね><
同じこと書いてる><
それで, float版とかも追加すると, また同じやつ書かなきゃいけない…面倒くさい!
そんな時にテンプレートの出番です(((o(゚▽゚)o)))

template<typename T>
T Add(T x, T y)
{
	return x + y;
}

これでOKです(((o(゚▽゚)o)))
『T』のところに型名(intとか)が入るんですね!
(あと template<class T> でもOKです)

表示させてみます.

#include <iostream>

template<typename T>
T Add(T x, T y)
{
	return x + y;
}

auto main() -> int 
{
	std::cout << Add<int>(2, 1) << std::endl; // 3
	std::cout << Add<double>(2.5, 1.5) << std::endl; // 4
	std::cout << Add<float>(2.5, 1.5) << std::endl; // 4
}

Add<int>(2, 1)
こんな風に呼び出していますね!

関数名<型>(引数)

です!(((o(゚▽゚)o)))

おまけ: Cプリプロセッサのマクロ版

一応C言語のプリプロセッサのマクロ版も書きます.
(読み飛ばしてOKです!)
C++がテンプレートをサポートし始めたのは1990年ごろで, それまでは #define マクロでテンプレートの真似事をしていたそうです.

本を読みながら書いててわけが分からないままに書いたんですけど, 動いたので大丈夫だと思います

#define name2(x, y) x##y
#define declare(x, y) name2(x, declare)(y)
#define implement(x, y) name2(x, implement)(y)

#define ADDdeclare(type) \
type ADD(type)(type x, type y);

#define ADDimplement(type) \
type ADD(type)(type x, type y) {\
    return x + y;\
}

#define ADD(type) name2(ADD, type)

// 宣言
declare(ADD, int);
declare(ADD, double);

// 実装
implement(ADD, int);
implement(ADD, double);

コード参照: C++テンプレートテクニック
(この本ではx,yのうち大きい方を返す MAX(type)(x, y) が載っています)

本来は1行に書かないといけないので, 改行するときは \ で改行です. 繋がってるんだよ〜ってしるしです
あと1行目の『##』はトークン連結です. name2は x と y を xy と連結させる処理をしています.

実行させます.
ADD(int)(2, 1)
こんな風に呼び出します

#include <iostream>

#define name2(x, y) x##y
#define declare(x, y) name2(x, declare)(y)
#define implement(x, y) name2(x, implement)(y)

#define ADDdeclare(type) \
type ADD(type)(type x, type y);

#define ADDimplement(type) \
type ADD(type)(type x, type y) {\
    return x + y;\
}

#define ADD(type) name2(ADD, type)

// 宣言
declare(ADD, int);
declare(ADD, double);

// 実装
implement(ADD, int);
implement(ADD, double);

auto main() -> int 
{
	std::cout << ADD(int)(2, 1) << std::endl; // 3
	std::cout << ADD(double)(2.5, 1.5) << std::endl; // 4
}

でもマクロは型推論もできないし, 型安全でもないです. 文字列操作によってソースコードを生成するだけですから….
なので, マクロではなく, テンプレート使いましょう! って話みたいです.

コンパイル時計算とは

普通は実行時に計算されます!
たとえば

#include <iostream>

auto main() -> int
{
    auto value1 = 2, value2 = 3;
    std::cout << (value1 + value2) << std::endl; // 5
}

この2+3は実行時に計算されます.
コードがコンパイルされて, リンクされて, 実行可能ファイルになって,
そんで実行される! その時に初めて計算される!

これが普通です.

で,
一方, コンパイル時計算とは,
その名の通り, コンパイル時に計算されます!

Compile-time programming
The preprocessor allows certain calculations to be carried out at compile time, meaning that by the time the code has finished compiling the decision has already been taken, and can be left out of the compiled executable.

引用元: wikipedia(en)

コンパイルが終わった時には既に計算が完了済みなのだ…!
で, 計算済みの値は, 定数として実行可能ファイルに組み込まれています.

コンパイル時処理される計算:
2 + 3;

実行可能ファイル(.exe)では:
5;
と, 定数として実行可能ファイルに組み込まれている

なるほど〜φ(゚▽゚)oメモメモ

そんで, Cのプリプロセッサのマクロとか, C++のテンプレートとかは, コンパイル時に展開されます!

#include <iostream>
template<int value1, int value2>
struct Add
{
   enum { result = value1 + value2 };
};
 
auto main() -> int
{
    std::cout << Add<2, 3>::result << std::endl; // 5
}

↓ あと, 全然関係ないんですけど, 吹いたツイート

メタプログラミング

C++での最初(最古)のメタプログラミングは,
[ コンパイルエラーメッセージが素数の列 ]
というものでした. C++委員会の席で Erwin Unruh が示したものだそうです.
参考: 翔泳社『C++テンプレートメタプログラミング』P4
↓この本

では『メタプログラミング』をWikipediaで調べてみます!

メタプログラミング (metaprogramming) とはプログラミング技法の一種で、ロジックを直接コーディングするのではなく、あるパターンをもったロジックを生成する高位ロジックによってプログラミングを行う方法、またその高位ロジックを定義する方法のこと。主に対象言語に埋め込まれたマクロ言語によって行われる。

引用元: メタプログラミング | wikipedia

… (((o(゚▽゚ ; )o)))
… (((o(゚▽゚)o)))

何を言っているのかサッパリ分からん

ヽ(;▽;)ノむずかしい!

なのでwikipedia(英語版)を見ますヽ(;▽;)ノ

Metaprogramming is the writing of computer programs with the ability to treat programs as their data. It means that a program could be designed to read, generate, analyse and/or transform other programs, and even modify itself while running. In some cases, this allows programmers to minimize the number of lines of code to express a solution (hence reducing development time), or it gives programs greater flexibility to efficiently handle new situations without recompilation.

引用元: Metaprogramming | wikipedia

まだ(私には)難しいけど, まあ, つまりは, 『コードを生成するプログラムを書くこと』ってことかなあ><
他のプログラムに変化(transform)したり, 実行時に自分自身を書き換えちゃったりするプログラムのことをいうらしい! メタプロすることによってプログラマが受ける恩恵は, コードを小さくしたり, (それによって開発の時間削ったり,) 新しい状況になったときに柔軟なhandleができるようになる!
ふむふむφ(゚▽゚)oメモメモ

テンプレートメタプログラミング

テンプレート使ってメタプロすることかな?(((o(゚▽゚)o)))
一応Wikipedia見る〜(((o(゚▽゚)o)))

テンプレートメタプログラミング(英: template metaprogramming)は、メタプログラミング技法の一種であり、コンパイラがテンプレートを使って一時的ソースコードを生成し、それを他のソースコードと結合してコンパイルする方式である。テンプレートが出力するものは、コンパイル時の定数、データ構造、関数定義などがある。テンプレートの利用は言わばコンパイル時の実行である。この技法は様々な言語で使われている(C++、D言語、Eiffel、Haskell、ML、XLなど)。

引用元: テンプレートメタプログラミング | wikipedia

相変わらずWikipediaわからん!!!!!!!ヽ(;▽;)ノむずかしいヽ(;▽;)ノこれ頭良い人向けのやつだ! いや, 理解できないのは単純に私がアホだからかな…

まあ, テンプレート使ってメタプロすること, って理解のまま進めます!(((o(゚▽゚)o)))

C++のテンプレートのミソは, 引数に型だけでなく値を渡せることなんです!

上で書いたテンプレートのコード

#include <iostream>

template<typename T>
T Add(T x, T y)
{
	return x + y;
}

auto main() -> int 
{
	std::cout << Add<int>(2, 1) << std::endl; // 3
	std::cout << Add<double>(2.5, 1.5) << std::endl; // 4
	std::cout << Add<float>(2.5, 1.5) << std::endl; // 4
}

Add<int>(2, 1)

こんな風に<int>って型を渡してたけど, ここに1とか10とか値を渡せるんです.

Func<3>::value;

こんな風に!

宣言も

template<typename T>
T Func(T x, T y)
{

ってやってたんですけど,

template <int N>
struct Func
{

みたいになります!
<typename T> → <int N>
と, 値を渡す感じになっていますね!

メタプロサンプルコード1: 足し合わせる

今まで出てきた, ふたつの引数を足し合わせる関数Addを書きます! コンパイル時Add!

#include <iostream>

template<int X, int Y>
struct Add
{
   enum { result = X + Y };
};

auto main() -> int
{
    std::cout << Add<2, 4>::result << std::endl; // 6
}

メタプロサンプルコード2: 階乗

サンプルコードとして階乗のコードを示します(((o(゚▽゚)o)))
コンパイル時階乗です!!

#include <iostream>

template <unsigned n>
struct Factorial
{
  enum { value = n * Factorial<n-1>::value };
};

template <>
struct Factorial<0>
{
  enum { value = 1 };
};

auto main() -> int 
{
	std::cout << Factorial<6>::value << std::endl; // 720
}

Factorial<6>::value → 720

です(((o(゚▽゚)o)))

Factorialはコンパイル時に計算されるので, main関数内に
int array[ factorial<7>::value ];
などと書いて, 配列のサイズ定義として使うこともできます!

あと特筆すべきこととして, 特殊化ができるということです. パターンマッチみたいな感じでしょうか.
上のコードでいうと,
struct Factorial<0>
これは渡ってきた引数が0のときの処理ですよってやつです. 再帰の基底部になっています.

Haskellで書いたこのコードと構成が似てる!

もっとテンプレートメタプログラミング

私はよくわからないんだけど, Boost Metaprogramming Library (MPL) っていう, メタプログラミングライブラリを使うともっと色々できるらしい!
あと中3女子さん(@bolero_MURAKAMI) がしきりに叫んでる『constexpr』とかいうやつもテンプレートメタプログラミングと何か関係があるらしいです. 全然わかんないんですけど…

以上です!(((o(゚▽゚)o)))メリー・クリスマス!