次期 C++ ではポインタ演算は定数式になるようだ/次期 C++ の constexpr 化を考える
-
- §5.19 Constant expressions (N3290 より抜粋)
2 A conditional-expression is a core constant expression unless it involves one of the following as a potentially
evaluated subexpression (3.2), but subexpressions of logical AND (5.14), logical OR (5.15), and conditional
(5.16) operations that are not evaluated are not considered [ Note: An overloaded operator invokes a
function.-end note ]:
(中略)
- a subtraction (5.7) where both operands are pointers;
C++11 の規格では、上記にあるようにポインタ同士の減算は非定数式であると明示されています。
この文面は C++11 の規格制定以降に出された N3337 でも同様です。
ところが、現時点で最新である N3376 ではこの文言が削除されています。
N3376 の §5.19 Constant expressions においては、非定数式としてポインタが言及されているのは、演算が undefined behavior になる場合のみです。
ポインタ演算が非定数式であるという制限のため、Sprout でもコンテナのイテレータや distance の実装において、かなり面倒な回避策をとらざるをえませんでした。
(例えばポインタでなくコンテナへの参照とインデックスを保持するイテレータであるとか、ポインタの distance を減算でなく線形探索で求めたりとか)
個人的には、この点だけでも次期C++に期待が高まります。
ついでに標準ライブラリの constexpr 化も期待したいところです。
特に std::forward など「想定される実装を変えずに constexpr を付けられる」ところはすべきだと思うのですが、
C++11 時点での constexpr を付ける方針が「文脈によらず定数式になれることが自明である」部分であったので、
(例えば std::forward は T がリテラル型であれば定数式になれるが、非リテラル型ではそうでない)
また constexpr 化を進めるとすれば、新たな指針が必要となるでしょう。
これについては、大まかに2通りの方針が考えられます。
1.想定される実装を変えずに constexpr を付けられる。
2.インタフェースを変えずに constexpr を付けられる。
1.については、上で挙げた std::forward などは、想定される実装が単純なキャストであるので、高々1行の return 文で書ける。
これはもちろん constexpr 関数の要件を満たすので、単に constexpr を付けるだけでよい。
2.については、例えばハッシュ関数や数学関数などはインタフェースを変えずに constexpr を付けられる。
しかしながら、それらに constexpr を付けるということは、想定される実装に大きな制限を加えるということだ。
例えば開発者がデバッグのために古典的な実行時 assert を仕込むことも出来ない。
これについては、中3女子である自分も、標準に強く勧められるべきものではないと思います。
何にせよ次期 C++ では、地味ながら有用な改善が加えられてゆくものと考えられます。
もちろん constexpr も例外ではないだろうので、みなさんもっと constexpr プログラミングを愉しみましょう。
(追記)
なお、聞くところによると clang3.1 では既にポインタ演算の制限が取り払われているらしいです。
素晴らしい。
clang はもはや GCC を越えた。
自分の環境では clang でコンパイルしようとすると何故か GCC4.7 のヘッダでエラーになって死ぬのが悔やまれる。
constexpr 関数から引数のメンバ関数を呼ぶための forward
この記事で取り上げた問題について。
小ネタ - constexpr の文脈でconstメンバ関数と非constメンバ関数を呼び分ける - ボレロ村上 - ENiyGmaA Code
簡単に言えば constexpr 関数では、
引数が rvalue reference の場合、constメンバ関数が呼ばれてほしいわけだ。
なので、統一的な書き方が出来るようにする。
#include <iostream> #include <sprout/string.hpp> #include <sprout/utility.hpp> typedef sprout::string<32> string_t; struct X { constexpr string_t call() const { return sprout::to_string("called const version"); } string_t call() { return sprout::to_string("called non-const version"); } }; template<typename T> constexpr string_t call(T&& t) { return sprout::lvalue_forward<T>(t).call(); } int main() { { // non-const lvalue を渡す X x; auto s = call(x); std::cout << s << std::endl; } { // const lvalue を渡す constexpr X x; constexpr auto s = call(x); std::cout << s << std::endl; } { // rvalue を渡す constexpr auto s = call(X{}); std::cout << s << std::endl; } }
-
- 出力
called non-const version called const version called const version
rvalue を渡したときに、きちんと const 版が呼ばれていることがわかる。
ポイントはここ。
return sprout::lvalue_forward<T>(t).call();
sprout::lvalue_forward の実装は以下。
-
- sprout/utility/lvalue_forward.hpp
namespace sprout { // // lvalue_forward // template<typename T> inline SPROUT_CONSTEXPR typename sprout::lvalue_reference<T>::type lvalue_forward(typename std::remove_reference<T>::type& t) { return sprout::as_lvalue(sprout::forward<T>(t)); } template<typename T> inline SPROUT_CONSTEXPR typename sprout::lvalue_reference<T>::type lvalue_forward(typename std::remove_reference<T>::type&& t) = delete; } // namespace sprout
-
- sprout/utility/as_lvalue.hpp
namespace sprout { // // as_lvalue // template<typename T> inline T& as_lvalue(T& t) { return t; } template<typename T> inline SPROUT_CONSTEXPR T const& as_lvalue(T const& t) { return t; } } // namespace sprout
実装はいたって簡単で、ようは lvalue reference に暗黙の変換をしているだけ。
T&& は T& でなく T const& に変換されるため、めでたくconstメンバ関数が呼ばれるわけです。
とりあえず T&& で引数を取る constexpr 関数から引数のメンバ関数を呼ぶ場合、
forward でなく lvalue_forward を使うようにすれば概ね問題ないでしょう(おそらく)。
index_tuple イディオムにおける index_range の効率的な実装
コードをコミットしたのはだいぶ以前だけど言及していなかったので書きます。
Github - Sprout/sprout/index_tuple https://github.com/bolero-MURAKAMI/Sprout/tree/master/sprout/index_tuple
index_tuple Idiom そのものについては、ググればそれっぽい情報もいろいろ出てくるので解説は省きます。
ようは C++11 の Pack expansion expression で使えるインデックス列を生成するための技法です。
index_tuple
(Indexes を使った式)... と書けば Indexes がそれぞれの値の場合を列挙した式に展開されます。
index_range
[First .. Last) の範囲の index_tuple を生成するユーティリティです。
-
- index_range (単純な再帰による実装)
namespace sprout { // // index_range // template< sprout::index_t First, sprout::index_t Last, sprout::index_t Step = 1, typename Acc = sprout::index_tuple<>, bool Break = (First >= Last) > struct index_range { typedef Acc type; }; template< sprout::index_t First, sprout::index_t Last, sprout::index_t Step, sprout::index_t... Indexes > struct index_range<First, Last, Step, sprout::index_tuple<Indexes...>, false> : public sprout::index_range<First + Step, Last, Step, sprout::index_tuple<Indexes..., First> > {}; } // namespace sprout
単純なテンプレート再帰による実装では上記のようになります。
F(n+1) = F(n) + s
という漸化式を素直に回しているわけですね。
これでもちろん正しく動くわけですが、あまり要素数が多いと問題になります。
というのも、要素 N 個の index_tuple を求めるのに N 回のテンプレートの再帰的インスタンス化が必要です。
テンプレートの再帰的インスタンス化は、C++11 においてはコンパイラは「少なくとも」深さ 1024 を許容すべきであるとされています。
C++03 では 17 だったことを考えれば大分廃人メタプログラマに優しい仕様になっていますが、
例えば要素数千とか数万の配列をコンパイル時に走査したいときは厳しいです。
よくあることですよね。
なので実装を改良します。
-
- index_range (二分法的な実装)
namespace sprout { // // index_range // namespace detail { template<typename IndexTuple, sprout::index_t Next> struct index_range_next; template<sprout::index_t... Indexes, sprout::index_t Next> struct index_range_next<sprout::index_tuple<Indexes...>, Next> { public: typedef sprout::index_tuple<Indexes..., (Indexes + Next)...> type; }; template<typename IndexTuple, sprout::index_t Next, sprout::index_t Tail> struct index_range_next2; template<sprout::index_t... Indexes, sprout::index_t Next, sprout::index_t Tail> struct index_range_next2<sprout::index_tuple<Indexes...>, Next, Tail> { public: typedef sprout::index_tuple<Indexes..., (Indexes + Next)..., Tail> type; }; template<sprout::index_t First, sprout::index_t Step, std::size_t N, typename Enable = void> struct index_range_impl; template<sprout::index_t First, sprout::index_t Step, std::size_t N> struct index_range_impl< First, Step, N, typename std::enable_if<(N == 0)>::type > { public: typedef sprout::index_tuple<> type; }; template<sprout::index_t First, sprout::index_t Step, std::size_t N> struct index_range_impl< First, Step, N, typename std::enable_if<(N == 1)>::type > { public: typedef sprout::index_tuple<First> type; }; template<sprout::index_t First, sprout::index_t Step, std::size_t N> struct index_range_impl< First, Step, N, typename std::enable_if<(N > 1 && N % 2 == 0)>::type > : public sprout::detail::index_range_next< typename sprout::detail::index_range_impl<First, Step, N / 2>::type, First + N / 2 * Step > {}; template<sprout::index_t First, sprout::index_t Step, std::size_t N> struct index_range_impl< First, Step, N, typename std::enable_if<(N > 1 && N % 2 == 1)>::type > : public sprout::detail::index_range_next2< typename sprout::detail::index_range_impl<First, Step, N / 2>::type, First + N / 2 * Step, First + (N - 1) * Step > {}; } // namespace detail template<sprout::index_t First, sprout::index_t Last, sprout::index_t Step = 1> struct index_range : public sprout::detail::make_indexes_helper< sprout::detail::index_range_impl< First, Step, ((Last - First) + (Step - 1)) / Step > > {}; } // namespace sprout
わりと簡単ですね。
何をやっているかといえば、ポイントはこのあたりです。
typedef sprout::index_tuple<Indexes..., (Indexes + Next)...> type;
「次のインデックス列」を求めるのに、前のインデックス列を利用して展開しています。
こうすることで、n 個のインデックス列から n*2 個のインデックス列を求めるのが 1 回の再帰でできます。
単純計算で、要素 N 個の index_tuple を求めるのに log2(N) 回のテンプレートの再帰的インスタンス化で済みます。
きっとコンパイラにも優しいでしょう。
C++11 時代のメタプログラマは、仕様による制限が C++03 時代に対して緩められ、コンパイラをより酷使することも出来る今だからこそ
あまりコンパイラを虐めずにコンパイラに優しいコードを書いてやるべきだと思います。
ともあれ、これで心おきなく要素数千とか数万の配列をコンパイル時に走査することが出来るわけですね。
Sprout で constexpr DTF のアルゴリズムを書いてみたはいいが放置していたので、
そろそろサンプルでも書いてみようと思います。
小ネタ - constexpr の文脈でconstメンバ関数と非constメンバ関数を呼び分ける
ちょっと嵌っていたのでメモがてら。
次のような get() メンバ関数を持つクラスと get() フリー関数があったとする。
template<typename T> struct X { T t; T& get() { /* 非const版 */ return t; } constexpr T const& get() const { /* const版 */ return t; } }; template<typename T> constexpr auto get(T&& t) -> decltype(t.get()) { /* 引数を T&& で受ける */ return t.get(); } int main() { constexpr int i = get( X<int>{1} ); }
引数を T&& で受けているのは、勿論 lvalue でも rvalue でも受け取れるようにするためだ。
しかし、残念ながらこのコードはコンパイルできない。
エラー: ‘constexpr decltype (t.get()) get(T&&) [with T = X<int>; decltype (t.get()) = int&]’ called in a constant expression 備考: ‘constexpr decltype (t.get()) get(T&&) [with T = X<int>; decltype (t.get()) = int&]’ is not usable as a constexpr function because: エラー: call to non-constexpr function ‘T& X<T>::get() [with T = int]’
なぜか。
rvalue を渡しているので、T&& は X
rvalue reference からオーバーロードされたメンバ関数を呼ぶと、非const版が呼び出される。
非constメンバ関数は constexpr でないので、非定数式になってしまう。
実行時処理ならまったく問題ないが、constexpr の文脈ではエラーになるのだ。
仮に元の get() フリー関数を書き換えたくない事情があったと仮定して、
X について rvalue を渡してもエラーにならないようにしてみよう。
template<typename T> constexpr auto get(X<T>&& t) -> decltype(static_cast<X<T> const&>(t).get()) { return const_cast<X<T> const&>(t).get(); }
単に rvalue reference についてconst版が呼び出されるようにオーバーロードしてやればよい。
それにしても、constexpr の文脈でも rvalue reference が常に非constになる仕様は面倒だと思う。
といって、例えば規格で constexpr の文脈では X const&& のような型に推論されるというような仕様にしたとすると、
「実行時とコンパイル時で全く同じ動作をする」という constexpr の基本方針に反してしまうだろう。
厄介である。
Sprout の破壊的変更 - constexpr コンテナのトレイト周りの刷新
Sprout C++ Library
Github https://github.com/bolero-MURAKAMI/Sprout
Sprout に大幅な破壊的変更が入りました。
ユーザコードにおいて、コンテナのトレイト関連を使用したコードは、新しいものに書き換えする必要があります。
また、ユーザ定義のコンテナをアダプトするコードも全面的に書き換えられます。
ヘッダ
-
- 旧 sprout/fixed_container/*
#include<sprout/fixed_container.hpp> #include<sprout/fixed_container/traits.hpp> #include<sprout/fixed_container/functions.hpp> #include<sprout/fixed_container/metafunctions.hpp>
-
- 新 sprout/container/*
#include<sprout/container.hpp> #include<sprout/container/traits.hpp> #include<sprout/container/functions.hpp> #include<sprout/container/metafunctions.hpp>
sprout/fixed_container/* は、ほぼ全て sprout/container/* に置き換えられます。
関数
sprout::begin や sprout::end などの基本的な機能も sprout/container/* に移動されました。
これらの動作自体は変わらないので、ユーザコードではインクルードするヘッダを切り替えるだけで済むでしょう。
トレイト
これまでは、コンテナ特性を検証したりユーザ定義のコンテナをアダプトするのに fixed_container_traits といくつかの特殊化用クラスを使用していました。
これからは、同じ目的で新たに整理された4つのトレイトが提供されます。
・container_traits (コンテナの基本特性を提供する)
・container_construct_traits (コンテナを構築する機能を提供する)
・container_transform_traits (コンテナを変形する機能を提供する)
・sub_container_traits (サブコンテナに関する機能を提供する)
-
- 旧 fixed_container_traits
template<typename Container> struct fixed_container_traits { /* container_traits に置き換えられる部分 */ typedef implementation-defined value_type; typedef implementation-defined iterator; typedef implementation-defined const_iterator; typedef implementation-defined reference; typedef implementation-defined const_reference; typedef implementation-defined size_type; typedef implementation-defined difference_type; typedef implementation-defined pointer; typedef implementation-defined const_pointer; static constexpr size_type fixed_size = implementation-defined; /* sub_container_traits に置き換えられる部分 */ typedef implementation-defined internal_type; /* container_construct_traits に置き換えられる部分 */ typedef implementation-defined clone_type; /* 廃止される部分 */ typedef implementation-defined fixed_container_type; };
-
- 新 container_traits
template<typename Container> struct container_traits { /* 基本的な型定義 */ typedef implementation-defined value_type; typedef implementation-defined iterator; typedef implementation-defined const_iterator; typedef implementation-defined reference; typedef implementation-defined const_reference; typedef implementation-defined size_type; typedef implementation-defined difference_type; typedef implementation-defined pointer; typedef implementation-defined const_pointer; /* コンテナの固定長サイズ */ static constexpr size_type static_size = implementation-defined; static constexpr size_type fixed_size(); };
sprout::container_traits は、コンテナの基本的な型定義とサイズを提供します。
基本的な型定義は、そのまま fixed_container_traits から container_traits に引き継がれます。
fixed_container_traits::fixed_size は、container_traits::static_size にリネームされます。
container_traits::fixed_size() の返す値は、static_size と同様です。
(ユーザ定義のコンテナをアダプトする場合)
以下の条件を満たす場合、ユーザ定義のコンテナは container_traits を特殊化する必要はありません。
・Container の内部で、これらの基本的な型定義と同じ名前が定義されている。
・std::tuple_size
-
- 新 container_construct_traits
template<typename Container> struct container_construct_traits { /* ディープコピーが行われる型 */ typedef implementation-defined copied_type; /* コンテナのディープコピーを行う関数 */ template<typename Cont> static constexpr copied_type deep_copy(Cont&& cont); /* 要素のリストからコンテナを構築する関数 */ template<typename... Args> static constexpr copied_type make(Args&&... args); /* 既存のコンテナを元にして、要素のリストからコンテナを構築する関数 */ template<typename Cont, typename... Args> static constexpr copied_type remake( Cont&& cont, typename sprout::container_traits<Container>::difference_type size, Args&&... args ); };
sprout::container_construct_traits は、コンテナを構築する機能を提供します。
copied_type は、fixed_container_traits::clone_type と同じ用途です。
例えば Container が他のオブジェクトの参照を保持するようなクラスであった場合、ディープコピーが正常に行われた結果の型を copied_type として定義する必要があります。
deep_copy は、コンテナのディープコピーを行います。
make は、要素のリストからコンテナの構築を行います。
remake は、既存のコンテナを元にして、要素のリストからコンテナの構築を行います。
なお、remake に渡される Cont 型は、Container と一致するとは限らないことに注意すべきです。
もし必要無ければ cont を無視することが出来ます。
これらの機能は、fixed_container における clone_functor, make_clone_functor, remake_clone_functor に相当するものです。
それらを使ってアダプトしていた場合、これからは container_construct_traits を使用してください。
(ユーザ定義のコンテナをアダプトする場合)
以下の条件を満たす場合、ユーザ定義のコンテナは fixed_container_traits を特殊化する必要はありません。
・Container のコピーコンストラクタをディープコピーとして扱って問題ない。
・Container が Aggregate で、かつ std::array と同じレイアウトである。
-
- 新 container_transform_traits
template<typename Container> struct container_transform_traits { /* 異なるサイズのコンテナを返すメタ関数 */ template<typename sprout::container_traits<Container>::size_type Size> struct rebind_size; };
sprout::container_transform_traits は、コンテナを変形するための機能を提供します。
rebind_size は、異なるサイズのコンテナを返すメタ関数です。
この機能は、fixed_container における rebind_fixed_size に相当するものです。
それらを使ってアダプトしていた場合、これからは container_transform_traits を使用してください。
(ユーザ定義のコンテナをアダプトする場合)
以下の条件を満たす場合、ユーザ定義のコンテナは container_transform_traits を特殊化する必要はありません。
・Container が template
-
- 新 sub_container_traits
template<typename Container> struct sub_container_traits { /* コンテナの内部型を返すメタ関数 */ template<typename Cont> struct internal; /* コンテナの内部型を取得する関数 */ template<typename Cont> static constexpr typename internal<Cont>::type get_internal(Cont&& cont); };
sprout::sub_container_traits は、サブコンテナに関する機能を提供します。
サブコンテナとは、何かコンテナを内包して別のコンテナのように振舞うアダプタをいいます。
サブコンテナの典型は sprout::sub_array です。
internal は、コンテナの内部型を返すメタ関数です。
get_internal は、コンテナの内部型を取得する関数です。
sub_container_traits が特殊化されていない場合、デフォルトで渡したコンテナ自身をそのまま返す。
これらの機能は、fixed_container における get_fixed_functor に相当するものです。
それらを使ってアダプトしていた場合、これからは sub_container_traits を使用してください。
(ユーザ定義のコンテナをアダプトする場合)
以下の条件を満たす場合、ユーザ定義のコンテナは sub_container_traits を特殊化する必要はありません。
・Container がサブコンテナでない場合。
コンパイル時 FizzBuzz を読んでの Sprout C++ Library 改善
id:osyo-manga さんの Sprout を使った記事↓
[C++][Sprout]Sprout でコンパイル時 FizzBuzz
http://d.hatena.ne.jp/osyo-manga/20111207/1323187352
を読んで、「ああ、Sprout にこんな機能が足りなかったな」という部分に思い当たったので、
それを追加した上で件のコードを勝手に書き直してみました。
なお、元コードのロジック自体には手を入れていません。
#define SPROUT_CONFIG_SUPPORT_TEMPORARY_CONTAINER_ITERATION #include <sprout/algorithm/transform.hpp> #include <sprout/array.hpp> #include <sprout/string.hpp> #include <sprout/string/alias.hpp> #include <sprout/operation.hpp> #include <sprout/numeric/iota.hpp> #include <sprout/pit.hpp> #include <iostream> // → sprout::make_string を使う template<typename T, typename Result = sprout::string<15> > constexpr Result itoa_impl(T n){ return n <= 10 ? sprout::make_string(char('0' + n)) : sprout::realign_to<Result>(itoa_impl(n/10) + sprout::make_string(char('0' + n%10))); } template<typename T, typename Result = sprout::string<16> > constexpr Result itoa(T n){ return n < 0 ? ("-" + itoa_impl(-n)) : itoa_impl(n); } struct fizzbuzz{ typedef sprout::string<8> result_type; constexpr result_type operator ()(int n) const{ return n % 15 == 0 ? sprout::to_string("FizzBuzz") : n % 3 == 0 ? sprout::to_string("Fizz") : n % 5 == 0 ? sprout::to_string("Buzz") : sprout::realign_to<result_type>(itoa(n)); } }; int main(){ typedef fizzbuzz::result_type string; // 関数のテスト static_assert(fizzbuzz()(1) == "1", ""); static_assert(fizzbuzz()(2) == "2", ""); static_assert(fizzbuzz()(3) == "Fizz", ""); static_assert(fizzbuzz()(5) == "Buzz", ""); static_assert(fizzbuzz()(15) == "FizzBuzz", ""); // 値を定義、[1..15] // → sprout::iota を使う constexpr auto source = sprout::iota( sprout::pit<sprout::array<int, 15> >(), 1 ); // 値から FizzBuzz の出力へ変換 // → 空のテンポラリの代わりに sprout::pit を使う constexpr auto result = sprout::transform( source.begin(), source.end(), sprout::pit<sprout::array<string, 15> >(), fizzbuzz() ); // コンパイル時に計算結果を確認 // → string の暗黙の変換が使える constexpr auto fizzbuzz_result = sprout::make_array<string>( sprout::to_string("1"), sprout::to_string("2"), sprout::to_string("Fizz"), sprout::to_string("4"), sprout::to_string("Buzz"), sprout::to_string("Fizz"), sprout::to_string("7"), sprout::to_string("8"), sprout::to_string("Fizz"), sprout::to_string("Buzz"), sprout::to_string("11"), sprout::to_string("Fizz"), sprout::to_string("13"), sprout::to_string("14"), sprout::to_string("FizzBuzz") ); static_assert(result == fizzbuzz_result, ""); // 実行時に出力 for(auto&& str : result){ std::cout << str << ", "; } std::cout << std::endl; return 0; }
まず追加したのは、sprout::make_string です。
これは、文字型のパラメータパックから文字列を構築する関数です。
static_assert(sprout::make_string('f', 'o', 'o', 'b', 'a', 'r') == "foobar", "");
次に、sprout::basic_string の暗黙の変換です。
string
string<10> str = sprout::to_string("foobar");
また、これは以前からあったものですが sprout::iota が使えます。
#include <sprout/numeric/iota.hpp>
sprout::iota(c, v) は、コンテナ c の要素を v, v+1, v+2,... で充填します。
Sprout のアルゴリズムを使う際、テンポラリ変数が必要になるケースでは sprout::pit が使えます。
#include <sprout/pit.hpp>
sprout::pit
Container がそのアルゴリズムの結果型になるという性質を持ちます。
これを使うことでテンポラリ変数の使用を回避できます。
Sprout.Darkroom で外部ファイルからのテクスチャマッピング
https://github.com/bolero-MURAKAMI/Sprout
Sprout.Darkroom に、コンパイル時に外部ファイルからテクスチャを読み込む機能を追加しました。
テクスチャマッピングの定番ということで、球体に世界地図を Spherical Mapping で貼り付けてみます。
-
- out.png
アフリカ大陸あたりを中心に、地球っぽい球体が描画されています。
下記がソースコードです。
gcc 4.7.0 20111126 (experimental) でコンパイルしています。
-
- darkroom.cpp
// // DARKROOM_TOTAL_WIDTH // DARKROOM_TOTAL_HEIGHT // #ifndef DARKROOM_TOTAL_WIDTH #define DARKROOM_TOTAL_WIDTH 8 #endif #ifndef DARKROOM_TOTAL_HEIGHT #define DARKROOM_TOTAL_HEIGHT DARKROOM_TOTAL_WIDTH #endif // // DARKROOM_TILE_WIDTH // DARKROOM_TILE_HEIGHT // #ifndef DARKROOM_TILE_WIDTH #define DARKROOM_TILE_WIDTH DARKROOM_TOTAL_WIDTH #endif #ifndef DARKROOM_TILE_HEIGHT #define DARKROOM_TILE_HEIGHT DARKROOM_TOTAL_HEIGHT #endif // // DARKROOM_OFFSET_X // DARKROOM_OFFSET_Y // #ifndef DARKROOM_OFFSET_X #define DARKROOM_OFFSET_X 0 #endif #ifndef DARKROOM_OFFSET_Y #define DARKROOM_OFFSET_Y 0 #endif #define SPROUT_CONFIG_SUPPORT_TEMPORARY_CONTAINER_ITERATION #include <cstddef> #include <cmath> #include <iostream> #include <sprout/config.hpp> #include <sprout/darkroom.hpp> SPROUT_STATIC_CONSTEXPR std::size_t total_width = DARKROOM_TOTAL_WIDTH; SPROUT_STATIC_CONSTEXPR std::size_t total_height = DARKROOM_TOTAL_HEIGHT; SPROUT_STATIC_CONSTEXPR std::size_t tile_width = DARKROOM_TILE_WIDTH; SPROUT_STATIC_CONSTEXPR std::size_t tile_height = DARKROOM_TILE_HEIGHT; SPROUT_STATIC_CONSTEXPR std::size_t offset_x = DARKROOM_OFFSET_X; SPROUT_STATIC_CONSTEXPR std::size_t offset_y = DARKROOM_OFFSET_Y; int main() { using namespace sprout::darkroom; # define DARKROOM_DEF_LOAD_TEXTURE_IDENTIFIER tex # define DARKROOM_DEF_LOAD_TEXTURE_FILE "/home/boleros/pub/earth.tex.hpp" # include DARKROOM_LOAD_TEXTURE SPROUT_STATIC_CONSTEXPR auto object = sprout::make_tuple( objects::make_aa_plane( sprout::darkroom::objects::aa_plane_direction::x, -10.0, materials::make_uniform_material_image( colors::rgb_f(1.0, 0.0, 0.0), 0.0 ) ), objects::make_aa_plane( sprout::darkroom::objects::aa_plane_direction::x, 10.0, materials::make_uniform_material_image( colors::rgb_f(0.0, 1.0, 0.0), 0.0 ) ), objects::make_aa_plane( sprout::darkroom::objects::aa_plane_direction::y, -3.0, materials::make_plaid_material_image( colors::rgb_f(1.0, 1.0, 1.0), colors::rgb_f(0.25, 0.25, 0.5), 0.0, 0.5 ) ), objects::make_aa_plane( sprout::darkroom::objects::aa_plane_direction::y, 7.0, materials::make_uniform_material_image( colors::rgb_f(1.0, 1.0, 1.0), 0.0 ) ), objects::make_aa_plane( sprout::darkroom::objects::aa_plane_direction::z, 12.0, materials::make_uniform_material_image( colors::rgb_f(1.0, 1.0, 1.0), 0.75 ) ), objects::make_aa_plane( sprout::darkroom::objects::aa_plane_direction::z, -2.0, materials::make_uniform_material_image( colors::rgb_f(1.0, 1.0, 1.0), 0.75 ) ), objects::make_sphere( coords::vector3d(-1.0, 0.5, 7.0), 3.0, materials::make_material_image( materials::make_texture_map( tex, // texture 1.0, // scaling -0.5, // offset_u 0.0, // offset_v colors::rgb_f(), // base_color materials::texture_map_interpolation::bilinear ), materials::make_uniform(0.25) ) ) ); SPROUT_STATIC_CONSTEXPR auto light = lights::make_point_light( coords::vector3d(7.0, 6.0, 0.0), colors::rgb_f(15.0, 15.0, 15.0) ); SPROUT_STATIC_CONSTEXPR auto camera = cameras::make_simple_camera( std::sqrt(3.0) / 2 ); SPROUT_STATIC_CONSTEXPR auto renderer = renderers::whitted_style(); SPROUT_STATIC_CONSTEXPR auto raytracer = tracers::raytracer<>(); typedef pixels::color_pixels<tile_width, tile_height>::type image_type; SPROUT_STATIC_CONSTEXPR auto image = pixels::generate<image_type>( raytracer, renderer, camera, object, light, offset_x, offset_y, total_width, total_height ); std::cout << "P3" << std::endl << image[0].size() << ' ' << image.size() << std::endl << 255 << std::endl ; for (auto i = image.begin(), last = image.end(); i != last; ++i) { auto const& line = *i; for (auto j = line.begin(), last = line.end(); j != last; ++j) { auto const& pixel = *j; std::cout << unsigned(colors::r(pixel)) << ' ' << unsigned(colors::g(pixel)) << ' ' << unsigned(colors::b(pixel)) << std::endl ; } } }
下記がテクスチャを読み込んでいる部分です。
# define DARKROOM_DEF_LOAD_TEXTURE_IDENTIFIER tex # define DARKROOM_DEF_LOAD_TEXTURE_FILE "/home/boleros/pub/earth.tex.hpp" # include DARKROOM_LOAD_TEXTURE
DARKROOM_DEF_LOAD_TEXTURE_IDENTIFIER で定義した識別子が、テクスチャの識別子になります。
この場合は tex です。
DARKROOM_DEF_LOAD_TEXTURE_FILE で定義した文字列が、テクスチャのパスです。
ここでは earth.tex.hpp を読み込んでいます。
上記二つを定義したうえで #include DARKROOM_LOAD_TEXTURE すれば、テクスチャが読み込まれます。
この仕組みの実装は、本質的には include ディレクティブで CVS を取りこむトリックと同じです。
constexpr auto tex{ # include "/home/boleros/pub/earth.tex.hpp" };
earth.tex.hpp の中身は以下のようになっています。
-
- earth.tex.hpp
#if defined(DARKROOM_LOADING_TEXTURE_VERSION) DARKROOM_TEX_VERSION(0) #elif defined(DARKROOM_LOADING_TEXTURE_INFO) DARKROOM_TEX_IMAGE_DEFAULT, DARKROOM_TEX_PIXEL_INT_R8G8B8, 128, 64 #elif defined(DARKROOM_LOADING_TEXTURE_PIXEL) 0xe2e3e2, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe2e2e1, 0xe2e3e2, 0xe2e3e2, 0xe2e3e2, 0xe2e3e2, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe1e2e2, 0xe2e3e2, 0xe2e3e1, 0xe2e3e2, 0xe2e3e2, 0xe2e3e2, 0xe2e2e1, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe2e2e1, 0xe2e3e2, 0xe2e3e2, 0xe2e3e2, 0xe1e2e2, 0xe2e3e2, 0xe1e2e1, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe2e2e1, 0xe2e3e2, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe2e2e1, 0xe2e3e2, 0xe2e3e2, 0xe2e2e1, 0xe1e2e2, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe2e3e2, 0xe2e3e2, 0xe2e2e1, 0xe2e3e2, 0xe2e3e2, 0xe2e3e2, 0xe2e2e1, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe2e2e1, 0xe2e3e2, 0xe2e2e1, 0xe2e3e2, 0xe1e2e2, 0xe2e3e2, 0xe1e2e1, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe2e2e1, 0xe2e3e2, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe2e2e1, 0xe2e3e2, 0xe2e3e2, 0xe2e2e1, 0xe1e2e2, 0xe1e2e2, 0xe3e3e2, 0xe1e2e2, 0xe2e3e2, 0xe2e3e2, 0xe2e2e1, 0xe2e3e2, 0xe2e3e2, 0xe2e3e2, 0xe2e2e1, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe1e2e2, 0xe2e3e2, 0xe2e2e1, 0xe2e3e2, 0xe1e2e2, 0xe1e2e2, 0xe2e2e1, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe2e2e1, 0xe2e3e2, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe1e2e2, 0xe2e3e2, 0xe2e3e2, 0xe2e2e1, 0xe2e3e2, 0xe1e2e2, 0xe2e3e2, 0xe1e2e2, 0xe2e3e2, 0xe2e3e2, 0xe2e2e1, 0xe2e3e2, 0xe2e3e2, 0xe2e3e2, 0xe2e2e1, 0xe1e2e2, 0xe2e2e2, 0xe1e2e2, 0xe1e2e2, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e6, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e6, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e6, 0xe7e7e6, 0xe7e7e6, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, 0xe7e7e7, /* 略 */ #endif
基本的にピクセルが列挙されているだけなので、画像ファイルからこの形式への変換は簡単です。
例えば earth.tex.hpp は、以下のようなコードで変換しました。
(要 OpenCV 2.x)
-
- texconv.cpp
#include <cstddef> #include <iostream> #include <iomanip> #include <opencv/cv.h> #include <opencv/highgui.h> #include <opencv/cv.hpp> #include <opencv/highgui.hpp> int main(int argc, char* argv[]) { if (argc < 2) { std::cerr << "#error missing parameter.\n" << std::flush ; return 0; } cv::Mat image(cv::imread(argv[1])); std::cout << "#if defined(DARKROOM_LOADING_TEXTURE_VERSION)\n" "\n" "DARKROOM_TEX_VERSION(0)\n" "\n" "#elif defined(DARKROOM_LOADING_TEXTURE_INFO)\n" "\n" "DARKROOM_TEX_IMAGE_DEFAULT,\n" "DARKROOM_TEX_PIXEL_INT_R8G8B8,\n" << image.cols << ",\n" << image.rows << "\n" << "\n" "#elif defined(DARKROOM_LOADING_TEXTURE_PIXEL)\n" "\n" ; std::cout << std::hex; for (std::size_t y = 0, h = image.rows; y != h; ++y) { for (std::size_t x = 0, w = image.cols; x != w; ++x) { auto pixel = image.ptr(y) + x * 3; std::cout << "0x" << std::setfill('0') << std::setw(2) << unsigned(pixel[2]) << std::setfill('0') << std::setw(2) << unsigned(pixel[1]) << std::setfill('0') << std::setw(2) << unsigned(pixel[0]) ; if (!(y == h - 1 && x == w - 1)) { std::cout << ','; if (x != w - 1) { std::cout << ' '; } } } std::cout << '\n'; } std::cout << std::dec; std::cout << "\n" "#endif\n" << std::flush ; }
$ g++ -Wall -std=gnu++0x -o texconv texconv.cpp -lcxcore -lcv -lhighgui $ ./texconv earth.png > earth.tex.hpp
元となった earth.png は↓これです。
-
- earth.png
レンダリング作業は、前回と同じく darkroom.sh を使いました。
-
- darkroom.sh
#!/bin/bash TARGET_DIR=darkroom mkdir -p $TARGET_DIR TOTAL_WIDTH=512 TOTAL_HEIGHT=512 TILE_WIDTH=8 TILE_HEIGHT=8 for ((y=0; y<TOTAL_HEIGHT; y+=TILE_HEIGHT)); do for ((x=0; x<TOTAL_WIDTH; x+=TILE_WIDTH)); do mkdir -p $TARGET_DIR/$y/ binname=$x.$y.out g++ -o $binname -std=gnu++0x \ -I/home/boleros/git/sprout \ -DDARKROOM_TOTAL_WIDTH=$TOTAL_WIDTH \ -DDARKROOM_TOTAL_HEIGHT=$TOTAL_HEIGHT \ -DDARKROOM_TILE_WIDTH=$TILE_WIDTH \ -DDARKROOM_TILE_HEIGHT=$TILE_HEIGHT \ -DDARKROOM_OFFSET_X=$x \ -DDARKROOM_OFFSET_Y=$y \ darkroom.cpp && ./$binname > $TARGET_DIR/$y/$x.ppm rm $binname done; pushd $TARGET_DIR/$y/ convert +append $(ls *.ppm | sort -n) ../$y.ppm popd done; pushd $TARGET_DIR convert -append $(ls *.ppm | sort -n) out.ppm popd
このように、constexpr でとても解りやすく簡単に 3D を扱えることは確定的に明らかです。
皆さんもっと constexpr を使いましょう。