進め、自分。

ゲームプログラマがC++のことなどを。

TypeInfo改良

最近ちょいちょい趣味プログラムを書いている。
VisualStudioを2017から2019に更新して、
Clangも更新して、
とりあえずC++17が使える環境に。

コードは色々触ってるけど、最近は自前TypeInfo関連の改良を。
このブログで最初に投稿してたやつ。
dinks.hateblo.jp

このTypeInfoを使って実装している
「何かのポインタを格納できて、取り出すところで意図した型なら取り出せる」
っていうクラスについて、
本当に格納している型でしか取り出せなくて、
その基底クラスでは取り出せなかったのでなんとか対応してみた。

ただ、以下の制約があるのでなにか良い方法を思いついたら改善したい。

  • とあるクラスの基底クラス情報を、使う側が設定しないといけない。
  • 基底クラス情報は1つしか設定できない。

※type_traitsに、基底クラスを列挙してくれる base_of みたいなのが追加されたらなぁ…

tofu/AnyPtr_test.cpp at develop · fsawa/tofu · GitHub
tofu/AnyPtr.hpp at develop · fsawa/tofu · GitHub

このクラスは基本的にスマートポインタとして使いたいわけではないところで作ったけど、
次はこの辺に対応してみようかなと思っている。

右クリックメニューから、今日の日付フォルダを作成

Windowsの右クリックメニューから、今日の日付フォルダを作成できるようにしました。

f:id:dinks:20200710010736j:plain

「2020-07-10」といった名前のフォルダをすぐに作成できます。

以下のテキストを「MakeTodayFolder.reg」という名前で保存し、レジストリに登録しています。

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\shell\MakeTodayFolder]
@="今日の日付フォルダ作成"
"ExtendedSubCommandsKey"="Directory\\shell\\MakeTodayFolder\\sub"

[HKEY_CLASSES_ROOT\Directory\shell\MakeTodayFolder\sub]

[HKEY_CLASSES_ROOT\Directory\shell\MakeTodayFolder\sub\shell]

[HKEY_CLASSES_ROOT\Directory\shell\MakeTodayFolder\sub\shell\Type1]
@="yyyymmdd"

[HKEY_CLASSES_ROOT\Directory\shell\MakeTodayFolder\sub\shell\Type1\command]
@="cmd /c md %V%\\\\%%date:/=%%"

[HKEY_CLASSES_ROOT\Directory\shell\MakeTodayFolder\sub\shell\Type2]
@="yyyy-mm-dd"

[HKEY_CLASSES_ROOT\Directory\shell\MakeTodayFolder\sub\shell\Type2\command]
@="cmd /c md %V%\\\\%%date:/=-%%"

[HKEY_CLASSES_ROOT\Directory\shell\MakeTodayFolder\sub\shell\Type3]
@="yyyy_mm_dd"

[HKEY_CLASSES_ROOT\Directory\shell\MakeTodayFolder\sub\shell\Type3\command]
@="cmd /c md %V%\\\\%%date:/=_%%"



[HKEY_CLASSES_ROOT\Directory\Background\shell\MakeTodayFolder]
@="今日の日付フォルダ作成"
"ExtendedSubCommandsKey"="Directory\\shell\\MakeTodayFolder\\sub"

[HKEY_CLASSES_ROOT\DesktopBackground\shell\MakeTodayFolder]
@="今日の日付フォルダ作成"
"ExtendedSubCommandsKey"="Directory\\shell\\MakeTodayFolder\\sub"

角度クラス 解説

角度クラスが一通り実装できたので解説を。(3年越しだな…)

まずは使い方。

void func( Angle a )
{
  ...
}
~~~
Angle a(0.5f); // 180度
Degree d(180.f); // 180度
Radian r(pi); // 180度

// どれでもOK
func(a);
func(d);
func(r);

// 四則演算
//a += 0.1f; // NG 単位の不明な数値との算術は禁止
a += Angle(0.1f); // OK
a += d; // OK

// 比較
Angle(0.1f) == Angle(0.5f); // false
Angle(0.1f) < Angle(0.5f); // true

a.value(1.f); // 値セット
auto v = a.value(); // 値ゲット

auto a2 = a.toAngle(); // Angle型へ変換
auto d2 = a.toDegree(); // Degree型へ変換
auto r2 = a.toRadian(); // Radian型へ変換

a = Angle(-0.5f).abs(); // 絶対値 =0.5f

// 正規化 [0.0~1.0)の範囲に変換 = [0度~360度)
a = Angle(1.5f).normalize(); // 0.5f
a = Angle(-1.25f).normalize(); // 0.75f

// 正と負それぞれの方向で正規化 (-1.0~1.0) = (-360度~360度)
a = Angle(-1.25f).signedNormalize(); // -0.25f

// 角度差を計算(符号あり最短距離)[-180度~180度)
d = Degree(10.f).distanceFrom( Degree(350.f) ); // -20.f

// 指定した角度を中心に反転させた角度を計算する
d = Degree(10.f).reflect( Degree(30.f) ); // 50.f
d = Degree(350.f).reflect( Degree(10.f) ); // 390.f

// 三角関数
v = a.sin();
v = a.cos();
v = a.tan();

// 定数
d = Degree::Zero(); // 0
d = Degree::One();  // 360
d = Degree::Half(); // 180

ざっとこんな感じで。

続いて実装方法。
まずはコアなクラステンプレート部分。

template <typename T, template <typename> class Policy>
class BasicAngles;

Tは値の型。具体的にはfloatやdoubleです。
PolicyはAngle,Degree,Radianの特性を反映する部分ですね。
実際下記のように定義しています。

/// 正規化角度ポリシー
template <typename T>
struct AnglePolicy
{
	static constexpr T kCycle = static_cast<T>(1.0); ///< 1周の値
};

/// ラジアンポリシー
template <typename T>
struct RadianPolicy
{
	static constexpr T kCycle = Konst<T>::pi2; ///< 1周の値
};

/// デグリーポリシー
template <typename T>
struct DegreePolicy
{
	static constexpr T kCycle = static_cast<T>(360); ///< 1周の値
};

せっかくポリシー分けたんですけど、見ての通り1周の値しかないです。
まあ、3種類の違いってそこしかないですからね。

で、これらから次の定義をしています。

/// 正規化角度クラス
template <typename T>
using BasicAngle = detail::BasicAngles<T, detail::AnglePolicy>;

/// ラジアンクラス
template <typename T>
using BasicRadian = detail::BasicAngles<T, detail::RadianPolicy>;

/// デグリークラス
template <typename T>
using BasicDegree = detail::BasicAngles<T, detail::DegreePolicy>;

double型のRadianを作りたかったら、

BasicRadian<double>

を使います。

ここから更に、次の定義をしています。
自分は普段 double を使うことがほとんどないので、
floatが標準として扱いやすいようにですね。
構成としてはstd::stringとstd::basic_stringの関係を意識してます。

/// 標準の正規化角度クラス
using Angle = BasicAngle<float>;

/// 標準のラジアンクラス
using Radian = BasicRadian<float>;

/// 標準のデグリークラス
using Degree = BasicDegree<float>;

型を柔軟に吸収したい場合は次の関数を使います。

template <typename T>
constexpr BasicAngle<T>  MakeAngle( T value ) noexcept { return BasicAngle<T>(value); }

template <typename T>
constexpr BasicRadian<T>  MakeRadian( T value ) noexcept { return BasicRadian<T>(value); }

template <typename T>
constexpr BasicDegree<T>  MakeDegree( T value ) noexcept { return BasicDegree<T>(value); }

ざっと以上ですね。
実装の中身は下記のリンクからどうぞ。

tofu/Angle.hpp at develop · fsawa/tofu · GitHub
tofu/Angle.cpp at develop · fsawa/tofu · GitHub

角度クラス 実装その2

角度クラス、実装していったら
やっぱりラジアン、デグリー、360度を1.0で表す3種類を
1つのテンプレートで実装することになりました…

ひとまず現在の実装状態は↓から。

tofu/Angle.hpp at develop · fsawa/tofu · GitHub
tofu/Angle.cpp at develop · fsawa/tofu · GitHub

一通り実装できたら個々の実装解説みたいな記事を書こうかなと。

次は三角関数と角度反射計算を実装予定。

角度クラス 実装その1

少しずつですが、角度クラス実装してます。

以前の構想ではラジアン、デグリーと、360度を1.0で表す3種類を扱う予定でしたが、
改めて考えてみると、1.0で表すクラス1つでいいんじゃないかと思ってきました。

もちろん、ラジアンやデグリー値との相互変換は必要だし変換コストもかかりますが、
そんなに負荷は気にならないんじゃないなかーと。
正規化しようとしたら 1.0 で扱ってる方がやりやすかったりもするので。
まあ、もっと実装進めていったら考え変わるかもしれませんが…
※3種類を1つのクラステンプレートで実装する方がやりがいあって楽しいですし

今のところ

  • 四則演算
  • 比較演算子
  • 正規化
  • 最短距離計算

を実装しました。
次はラジアンやデグリーからの変換やらを実装予定。

あと、この角度クラスの定数値を定義しようとして、
constexprを少し調べてました。
クラスをconstexprとして扱うのはなかなかめんどくさいですねー。
いや、めんどくさいっていうか修飾子が多い。
コンストラクタが
constexpr explicit Hoge(...) noexcept {...}
とか修飾子だらけ。
c++11以降をバリバリ使っている方々はnoexceptとかまじめにつけてるもんなんでしょうか?

型情報クラス作成

型情報クラス、最低限必要なところは実装完了。
一応GitHubで見られます。
tofu/TypeInfo.hpp at develop · fsawa/tofu · GitHub
tofu/TypeInfo.cpp at develop · fsawa/tofu · GitHub

あとは参照型の対応と、継承関係の検出をやりたいけどそれはまた後で。
次は、ずっとやりたかった角度クラスの実装予定。

RTTIを使わない型名の取得

ひとまず単体テストのプロジェクトが作成できました。
コンパイラを「Clang with Microsoft CodeGen」にしていて、
Clang用のプロジェクトテンプレートにはライブラリしか無いので
後から実行形式に設定を変えるなど。
VS2017のClangがどういうバージョンなのかまだ調べて無いですが…。

テスト自体は最低限動かせるところまでと、
自作の型情報クラスの動作を軽く確認出来るところまで。

型情報クラスでは型名を文字列で取得出来るように対応しました。
あ、RTTIは使わない方針なので、typeidとかは使わずに
自前でゴリゴリ型名を抽出してます。

抽出方法ですが、下のコードで __PRETTY_FUNCTION__ のマクロの部分、
例えばテンプレート引数が int だった場合は
"TTypeInfo<int>::TTypeInfo() [T = int]"

という文字列に展開されます。

template <typname T>
class TTypeInfo
{
public:
	TTypeInfo()
	{
		__PRETTY_FUNCTION__; // ここ
	}
};

これは Clang の場合で、
msvcだと確か __FUNCTION__ マクロを使う必要があって、
"TTypeInfo<int>::TTypeInfo"
という文字列に展開されたはず。
おいおいmsvcにも対応するつもりなのでまたその時確認します。

それで、上記の文字列から型名部分を抽出して取得できるようにしています。
ソースコードはもう少し整理できたら公開しようと思ってます。
githubにはプッシュしてありますが。

次はもう少しTypeInfo用のテストコードを書く予定。