2012年12月23日日曜日

Software Development for Infrastructureの紹介

静的次元解析

Bjarne Stroustrupさんが、C++11仕様の決定を記念して書いたC++のコーディング・スタイルに関する記事 Software Development for Infrastructureで紹介されていた静的次元解析のコードを試しに実装してみた。

C++11スタイルでコードを書くと今までのスタイルとまったく違うのコードになるのに驚いた。しかし、 C++のテンプレート静的に(実行時のオーバーヘッドなしで)、ここまでできるとは素晴らしい。

上記記事、ここに挙げた静的次元解析以外にもC++で実装するときにスタイルについて、助言が書かれているので お勧めです。

 1:  #include <iostream>
 2:  
 3:  // 単位を表現するテンプレート・クラス。
 4:  template <int M, int K, int S>
 5:  struct Unit {
 6:    enum {m = M, kg = K, sec =S};
 7:  };
 8:  // 単位付き値を表現するテンプレート・クラス。
 9:  template <typename Unit>
10:  struct Value {
11:    double val;
12:    explicit constexpr Value(double b) : val(b) {}
13:  };
14:  
15:  using Speed = Unit<1,0,-1>;
16:  using Length = Unit<1,0,0>;
17:  using Time = Unit<0,0,1>;
18:  
19:  // -------------------------ユーザ定義リテラルの定義-----------------------------------------
20:  /*
21:    C++には組み込みのリテラルがありました。C++11から、ユーザがリテラルを定義できるようになりました。
22:  
23:    true; boolリテラル。
24:    "hello, world!"; 文字列リテラル。
25:    1l; longリテラル。
26:    1.0d; doubleリテラル。
27:  
28:    型安全なコードを書くために以下のようなリテラルを定義します。リテラルには(_)アンダースコアが
29:    含まれていることに注意。(_)を付けないリテラルは標準で予約さているので警告。
30:  
31:    30.0_km; 30 kmを表現するリテラル。
32:    2.0_hr; 2時間を表現するリテラル。
33:  */
34:  constexpr Value<Length>
35:  operator"" _km(long double d) {
36:    return Value<Length>(1000*d);// Lengthの基本単位がメートルなので、1000倍して、kmからmに変換。
37:  }
38:  
39:  constexpr Value<Time>
40:  operator"" _hr(long double d) {
41:    return Value<Time>(60*60*d);// Timeの基本単位が秒(sec)なので、時から秒に変換。
42:  }
43:  
44:  // ------------------------ 単位付き値に対する演算を定義。-------------------------------------
45:  template <typename Unit>
46:  Value<Unit> operator+(Value<Unit>&& lhs, Value<Unit>&& rhs) {
47:    return Value<Unit>(lhs.val + rhs.val);
48:  }
49:  
50:  template <typename Unit1, typename Unit2>
51:  Value<Unit<Unit1::m - Unit2::m, Unit1::kg - Unit2::kg, Unit1::sec - Unit2::sec>>
52:  operator/(Value<Unit1>& n, Value<Unit2>& d) {
53:    return Value<Unit<Unit1::m - Unit2::m, Unit1::kg - Unit2::kg, Unit1::sec - Unit2::sec>>(n.val/d.val);
54:  }
55:  
56:  // Valueが正しい単位を持っているか確認するための関数テンプレートisSpeed。
57:  template <typename T>
58:  constexpr bool isSpeed(){ return false; }
59:  
60:  template <>
61:  constexpr bool isSpeed<Value<Speed>>(){ return true; }
62:  
63:  // Valueを表示するために<<演算子をオーバーロード。
64:  template <typename Unit>
65:  std::ostream& operator<<(std::ostream& os, Value<Unit>& v){
66:    return (os << v.val << std::endl);
67:  }
68:  
69:  int main() {
70:    // 家からオフィスまでの通勤時の平均時速を求めたい。
71:    auto homeToOffice(30.0_km);// 家からオフィスまでの距離。30km
72:    auto time(1.0_hr);// 所要時間 1.0時間
73:    auto average_speed = homeToOffice/time;// 時速を計算。
74:    // 計算したaverage_speedの単位が正しいかチェック。
75:    static_assert(isSpeed<decltype(average_speed)>(),"Check failed!!");
76:    std::cout << average_speed << std::endl;
77:  
78:    // 距離と時間は足せない。コンパイル・エラー。
79:    // auto bug_1 = homeToOffice + time;
80:  
81:    // 時速を計算したいのに割り算が逆。static_assert部分でコンパイル・エラー。
82:    // auto bug_2 = time/homeToOffice;
83:    // static_assert(isSpeed<decltype(bug_2)>(),"Check failed!!");
84:  
85:    return 0;
86:  }