テンプレートで型情報を作る
C++勉強ネタです
テンプレートを使って、関数型言語の型情報を生成してみます(やっつけなのでバグがあるかも)
値型はValue, λ抽象はArrowになっています。 とりあえずValueには設定された文字列を格納し、Arrowは単にリストを作ってるだけです。
型Tが関数型を持つ場合は、std::tuple
にシグネチャを格納しておきます
静的変数のアドレスはconstexpr
として、初期化前に取得することができます。
依存関係のある静的変数を初期化するのって大変ですよね…
staticローカル変数を使っても良いのですが、そうすると初期化が再帰してUBになるかもです
#include <tuple> #include <iostream> #include <variant> #include <cassert> template <class T> struct traits; // value type struct Value; // arrow type struct Arrow; // union using Type = std::variant<Arrow,Value>; struct Arrow{ Type* captured; Type* returns; }; struct Value{ const char* name; }; template <class T> struct value_type{ static Type type; }; // arrow_type impl template <class T, class ...Ts> struct arrow_type_{ static Type type; }; // arrow_type impl template <class T1, class T2> struct arrow_type_<T1,T2>{ static Type type; }; template <class T, class ...Ts> struct arrow_type; template <class T, class ...Ts> struct arrow_type<T, std::tuple<Ts...>>{ static constexpr Type* type = &arrow_type_<Ts...>::type; }; template <class T, class = void> struct object_type { static constexpr Type* type = arrow_type<T,typename T::type>::type; }; template <class T> struct object_type<T, std::void_t<decltype(traits<T>::name)>>{ static constexpr Type* type = &value_type<T>::type; }; template <class T> Type value_type<T>::type {Value{traits<T>::name}}; template <class T, class ...Ts> Type arrow_type_<T,Ts...>::type {Arrow{ object_type<T>::type, &arrow_type<Ts...>::type }}; template <class T1, class T2> Type arrow_type_<T1,T2>::type {Arrow{object_type<T1>::type, object_type<T2>::type}}; // define int type template <> struct traits<int>{ static constexpr const char* name = "int"; }; int main () { struct foo{ // basic closure type (int->int) using type = std::tuple<int,int>; }; struct bar{ // recursive closure type (bar->int) using type = std::tuple<bar,int>; }; // value auto i = object_type<int>::type; std::cout << std::get<Value>(*i).name << std::endl; // int // closure auto f = object_type<foo>::type; assert(std::get<Arrow>(*f).captured == i); assert(std::get<Arrow>(*f).returns == i); // recursive auto b = object_type<bar>::type; assert(b == std::get<Arrow>(*b).captured); return 0; }
可変長テンプレートでunion
C++勉強ネタが続きます
templateを作って直和型もどきを作りたいときがあります。
これはすでにBoost Library
で実現されていて、boost::variant
として利用できます。
また、C++1z
では、これが標準に追加されます。
今回は、可変長テンプレートを使ってこれを実装してみたいと思い立ち書いてます。
boost::variant
はインデックス用の数字を保存しますが、今回は値だけを格納することにします。
とりあえず、データ構造はこんな感じでしょうか
U[3]
がU.nxt.nxt.nxt.v
になるみたいなイメージです
template <size_t N, class Head, class ...Tail> union U { Head v; U<N-1 ,Tail...> nxt; }; template <class Head,class ...Tail> union U<0,Head,Tail...> { Head v; };
これを初期化するクラスを作っておきます
template <class ...Args> class Union{ U<sizeof...(Args)-1,Args...> u; };
中身を取り出したいので、
auto v3 = get<3>(u);
みたいにかけるようにしたいですね
template <size_t N> struct _getter { template <size_t U_N, class U_Head, class... U_Tail> static auto get(const U<U_N,U_Head,U_Tail...>& u){ return _getter<N-1>::get(u.nxt); } }; template <> struct _getter<0>{ template <size_t U_N, class U_Head, class... U_Tail> static auto get(const U<U_N,U_Head,U_Tail...>& u){ return u.v; } }; template <size_t N, class...Args> auto get(const Union<Args...>& u){ return _getter<N>::get(u.u); }
うーん…
int main(){ union _u { char c; double d; int i; }; Union<char ,double ,int> uni; variant<char, double ,int> var; auto v0 = get<0>(uni); cout << sizeof(v0) << endl; // 1 cout << sizeof(_u) << endl; // 8 cout << sizeof(uni) << endl; // 8 cout << sizeof(var) << endl; // 16 }
unionと同サイズになることがわかりますね。
独自の型を実装していて、セレクタが不要な場合などに使えそう。
c++17のaggregate initialization
c++では、十分に「しょぼい」クラスに対して、aggregate initializationが使えるみたいです。
aggregate initializationはリスト初期化の一種で、
色々できることがありますが、例をあげると
struct Foo { int i; float f; };
というクラスは、
Foo f = { 4, 2. };
のようにして初期化できます。便利ですね。 c++17からは、これが更に拡張されるみたいです
例えば、
struct Header { long l; }; struct Foo : Header { int i; };
このいかにもC言語風の構造体は、
Foo f = { {4}, 2 };
もしくは
Foo f = { 4, 2 };
のように初期化できます。良いですね!
デフォルトテンプレート引数を持つクラスのstaticメンバを初期化する
template <class T, class = int> class Foo{ static int i; }; template <class T> int Foo<T>::i = 42;
これは動きません。
template <class T, class = int> class Foo{ static int i; }; template <class T, class E> int Foo<T,E>::i = 42;
こうやって全部きっちり書く必要があるみたいです
有理Bスプライン曲線の非有理化
今回はNURBS曲線の小ネタです
非有理化と言っていますが、正しくは計算過程で一時的に次元を上げることができるよ、という話です
これを利用すれば、有理化を考慮していないアルゴリズム(Bスプライン用の計算など)に、NURBSを適用することができます
有理化されたn次Bスプライン曲線を、非有理(すべての重さが1)のn+1次Bスプライン曲線に変換する
n次の有理Bスプライン曲線は、簡単にn+1次の非有理曲線に変換することができます。 NURBS曲線の定義を思い出してみましょう
注目するのは、の部分です。例えば、が三次元ベクトル
で、ウェイトが設定されているとすると、
となります。
ここで、 とすれば、です。 xyz成分が一致しているので、これでなんとかなりそうです。
問題は、重みが変更されてしまうので、分母が変わってしまうことです。はすべて1ですので、正規化係数の分母は1になり消えます
よって、このときのNURBSは
あら偶然、結果のw成分に、重みの変更で消えた分母が見えています。これで結果の全体を除算して、xyz成分だけを取り出すと、となり、正しい結果を得れることがわかります
まとめ
有理Bスプライン曲線を、Bスプライン曲線用のアルゴリズムに適用するときは、次のような手順で一時的にn+1次のBスプライン曲線に変換する
- n+1次の新しいベクトルを用意し、先頭n次元にn次ベクトルの制御点座標をコピーする
- コピーしたベクトルに、重さを掛ける
- コピーしたベクトルのn+1次元要素に、重さをコピーする
- (アルゴリズムを適用する)
- 結果全体を、結果のn+1次元要素で割る
- 結果の先頭n次元を取り出して終わり
多くの場合、重み付きn次元座標はn+1次元ベクトルとして保存されているので、コードにするとかなり単純になります。 ベクトルの計算やコピーは、最適化がきくかどうかなどで変わってくるので、一番早い方法を検証する必要がありそうです
type traits(型特性)クラスの利用
またまたc++勉強ネタ。
traitsクラス
traits
クラスは、template
を利用したテクニックの一つです。
標準ライブラリや、boostなどで多用される手法の一つでもあります。
今回は、traits
クラスの基本的な利用法を勉強したいと思います。
traitsクラス
type traits
(型特性)は、型に関する特性を表現するために利用されるクラスです。
traits
には色々なバリエーションがありますが、まず例として、STLのiterator_traits
クラステンプレートを見てみましょう.
traitsを利用した例 std::iterator_traits
std::iterator_traits
は、イテレータに関する型特性を表現するためのクラスで、以下のような構成をしています.
以下のコードはcpprefjp - C++日本語リファレンスから引用しました。
namespace std { template <class Iterator> struct iterator_traits { using difference_type = typename Iterator::difference_type; using value_type = typename Iterator::value_type; using pointer = typename Iterator::pointer; using reference = typename Iterator::reference; using iterator_category = typename Iterator::iterator_category; }; // ポインタに対する特殊化 template <class T> struct iterator_traits<T*> { using difference_type = ptrdiff_t; using value_type = T; using pointer = T*; using reference = T&; using iterator_category = random_access_iterator_tag; }; template<class T> struct iterator_traits<const T*> { using difference_type = ptrdiff_t; using value_type = T; using pointer = const T*; using reference = const T&; using iterator_category = random_access_iterator_tag; }; }
コードを見ると、iterator_traits
は単にtypedef
された型情報を格納しているだけで、メンバを持っていません.
使用者は、適切にこのテンプレートを特殊化することによって、iterator
用に作られたアルゴリズムを利用することができるようになります
traits活用: タグ・ディスパッチ
std::iterator_traits
には、タグ・ディスパッチのために利用する iterator_category
という型を持っています
iterator_category
には、タグ(tag)と呼ばれる空クラスを指定します
標準で用意されているクラスは以下
std::input_iterator_tag
std::output_iterator_tag
std::forward_iterator_tag
std::bidirectional_iterator_tag
std::random_access_iterator_tag
さて、このiterator_category
をどう使うかというと、関数が引数の型によって選択できることを利用します
//こんな感じでディスパッチさせる template <typename InputIterator> void foo(InputIterator itr, std::input_iterator_tag){ } template <typename OutputIterator> void foo(OutputIterator itr, std::output_iterator_tag){ } template <typename Iterator> void foo(Iterator itr){ foo(itr, typename std::iterator_traits<Iterator>::iterator_category()); }
また、std::forward_iterator_tag
などは他のイテレータタグを継承しています
タグが変換可能であれば呼び出しを行えるようになるので、継承がうまく利用されていますね
以上、type traits
の基本でした。この他にもstatic
関数と組み合わせたり、traits
には様々な活用法があるみたいです
いろんなライブラリをよんで勉強していきたいですね!
君はプライベートメンバにアクセスできるフレンズなんだね!
いまだc++を勉強中です。今回はちょっとマイナーな機能、friend
について
friend
friend
を使えば、クラスのプライベートメンバにアクセスできる外部関数を指定できます。
private
を指定すると、基本的に外部からのアクセスは禁止されるのですが、friend
を使えば特例を作ることができる機能です。
この機能を使わなければいけないことは少ないと思いますが、覚えておくと便利かもしれません。
ちなみに、friend
指定はメンバ関数のように行いますが、アクセスが許可されるだけで、関数自体はメンバではありません。
つまりthis
ポインタは渡されませんので、参照やコピーを行ってオブジェクトを関数に渡す必要があります。
もちろん、static
メンバであれば、インスタンスは不要です。
使いみちを考えてみましたが、ポインタでリンクを構築する時とか?
トリッキーなので、一から設計をする際には、あまり選択したくない方法ではありますね
サンプルコードを以下に示します。
#include <iostream> class A; class B; class C; class A{ private: friend bool connectAB( A& a, B& b); //フレンド指定は宣言のように行います B* ptrb = nullptr; }; class B{ private: friend bool connectAB(A& a, B& b); A* ptra = nullptr; }; class C{ private: friend bool connectAB(A& a,B& b); static void foo(){ std::cout << "すごーい!" << std::endl; } }; bool connectAB(A& a,B& b){ //privateにアクセスできます a.ptrb = &b; b.ptra = &a; C::foo(); //staticなら、インスタンスは不要 return true; } int main(){ A a; B b; connectAB(a,b); return 0; //出力: //すごーい! }