この記事はC++ Advent Calendar 2014の20日目の記事です.AdCのリンクは記事の下にあります.
I(@wx257osn2)です1.去年は残念な感じだったので今年こそはちゃんとした記事を書きたかったのですが,今年も間に合いませんでした…2.ところで去年の記事を見返してみて気付いたのですが,去年も20日担当だったんですね.来年も20日にしようかしら3.
tl;dr
- C++11 constexprな無名関数ライブラリを作ったのでリファレンスと実装解説を行います.
- 鳥小屋 is 便利
はじめに
当記事は2014年の暮れにも関わらずC++11の内容を記事にしています.新規格が出た年にもう3年も前の規格を記事にするのは些か憚られるのですが,GCCが「C++14 constexpr support」と発表した後1ヶ月に渡ってHEADがまともにconstexpr
をコンパイルできなくなった4,libstdc++はいつまでも仕事しない,というようにC++14をまともに使えるようになるのは当分先なんじゃないかなぁというのが個人的な感想であります5.そんなわけで,当記事ではC++11を扱います.ご了承ください. C++14使いたい
問題提起
さて,以下のような問題があったとします.
10個の要素を持つ
int
の配列array
の中から奇数がいくつあるか数えるコードを書け
それに対するC++03時代の解答は以下の様なものになるかと思います,多分.6
#include<boost/lambda/lambda.hpp>
#include<boost/range/algorithm.hpp>
using boost::lambda::_1;
const int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
const int result = boost::count_if(array, _1 % 2 == 1);
rangeやlambda,便利ですよね.
それでは,C++11時代ではどのような回答になるでしょうか.
#include<sprout/range/algorithm/count_if.hpp>
#include<sprout/array.hpp>
#include<sprout/functional.hpp>
using sprout::placeholders::_1;
static constexpr auto array = sprout::array<int, 10>{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}};
static constexpr auto result = sprout::range::count_if(array, sprout::cbind(sprout::modulus<>(), _1, 2));
C++11からはconstexpr
の登場によってコンパイル時処理が随分楽に書けるようになったので,この程度のことはコンパイル時に済ませるべきです.Sprout,大変便利ですね.
しかし,Sproutではrange-based algorithmは実装されていますが,lambdaは実装されていません.そのため,非constexprでは_1 % 2 == 1
と書けるところが,constexprにしようとすると(最大限using
しても)cbind(modulus<>(), _1, 2)
になってしまっています.比較してみるとラムダ式の方がかっこいい[要出典]です.ラムダ式で書きたいですよね?
これを解消するためには,constexpr
に対応した無名関数ライブラリが必要となります.
作りました.
Veiler.Lampads
Veiler.LampadsはC++11のconstexpr lambdaライブラリです.まずはサンプルをご覧ください.
上述の例 / 簡易テスト //鳥小屋はconstexpr-Lambdaを搭載した唯一のオンラインコンパイラです
リファレンス
それでは,簡易なリファレンスを以下に示します.
基本関数
まず,Lampadsでは基本関数と演算子を組み合わせて使いたい無名関数を構築します.基本関数とは,以下に挙げる特別な名前と機能を持った関数です.本記事では便宜上,基本関数は(名前を有していますが)無名関数に含まれるとして扱います.
Placeholder
与えられた引数を返します.n_
と記述することで第n引数を返します.(n_∈{m_∈Z : _m_≧1})
例えば,1_
なら第1引数,2_
なら第2引数…となります.
使用する前にusing veiler::lampads::udl::operator"" _;
が必要です.7
using veiler::lampads::udl::operator"" _;
static_assert(1_(1, 2, 3) == 1, ""); //第1引数を返す関数
Variadic Placeholder
SelfとBind,Polymorphic Member Function Accessorにおいてのみ使用できる関数です.n_tail
と記述することで第(n+1)引数以降の引数列を返します.(n_∈{m_∈Z : _m_≧0})
つまり,例えば0_tail
と記述した場合,引数全てを表します.1_tail
なら第2引数以降の全ての引数を表します.
使用する前にusing veiler::lampads::udl::operator"" _tail;
が必要です.8
using veiler::lampads::udl::operator"" _tail;
using veiler::lampads::bind; //詳細は後述
static constexpr auto f = bind(std::printf, 0_tail); //fはprintfと(ほぼ)同義
f("%d", 1); //1
Number Of Arguments
与えられた引数の数をstd::size_t
で返す関数です.num_of_args
と記述します.
using veiler::lampads::num_of_args;
static_assert(num_of_args(1, 2, 3, 4) == 4ul, "");
Value
定数関数です.val(t)
或いはval<T>(args...)
と記述することで,任意の引数を与えた時にt
或いはT(args...)
を返します.また,veiler::ref(t)
を渡すことで参照を返す関数となります.
using veiler::lampads::val;
static_assert(val(1)(3, 4, 5) == 1, ""); //引数は全て無視されます
static_assert(val<veiler::tuple<int, double>>(1, 3.14)() == veiler::make_tuple(1, 3.14), "");
std::string str;
val(veiler::ref(str))() = "abc";
std::cout << str << std::endl; //abc
If Then Else
分岐関数です.if_(cond)[expr1].else_[expr2]
と記述します.(cond,_expr1_及び_expr2_はLampadsの無名関数)与えられた引数を用いて条件関数_cond_を実行し,戻り値がtrue
かfalse
かによって関数_expr1_及び関数_expr2_のいずれかを選択,与えられた引数を用いて呼び出します.
using veiler::lampads::udl::operator"" _;
using veiler::lampads::if_;
static constexpr auto cond = if_(1_)[2_].else_[3_]; //第1引数がtrueなら第2引数, falseなら第3引数
static_assert(cond(true, 1, 2) == 1, "");
Self
再帰関数です.self(args...)
と記述することで,「無名関数列_args..._のそれぞれの関数に対して,現在の引数を与えた結果」の列を引数として自身を再帰的に呼び出すことが出来ます.主にIf Then Elseと組み合わせて使います.
using veiler::lampads::udl::operator"" _;
using veiler::lampads::udl::operator"" _tail;
using veiler::lampads::if_;
using veiler::lampads::self;
using veiler::lampads::num_of_args;
static constexpr auto fact = //階乗関数
if_(1_ == 0)[ //第1引数が0なら
1 //1を返し
].
else_[ //そうでないなら
1_ * self(1_ - 1) //第1引数 * fact(第1引数 - 1)を返す
];
static_assert(fact(4) == 24, "");
static constexpr auto sum = //総和(左畳み込み再帰)
if_(num_of_args == 1ul)[ //引数の数が1つなら
1_ //第1引数を返し
].
else_[ //そうでないなら
self(1_ + 2_, 2_tail) //sum(第1引数 + 第2引数, 第3引数以降)を返す
];
static_assert(sum(1, 2, 3, 4, 5) == 15, "");
static_assert(sum(1.5, 2.5, 3.5, 4.5, 5.5) == 17.5, ""); //doubleでも動作
assert(sum(std::string("a"), "b", "c") == "abc"); //実行時文字列処理も可能
Bind
部分適用関数です.bind(f, args...)
と記述することで,「無名関数列_args..._のそれぞれの関数に対して,現在の引数を与えた結果」の列を引数として任意の関数_f_を呼び出すことが出来ます.
//GCCでは動作しない(関数ポインタ経由でconstexpr関数を呼び出すと定数式として認識されないバグがあるため)
using veiler::lampads::udl::operator"" _;
using veiler::lampads::val;
using veiler::lampads::bind;
static constexpr auto count_even = bind(
sprout::range::count_if<sprout::array<int, 5>, decltype(1_ % 2 == 0)>, //実体化して関数のアドレスを取る
1_, val(1_ % 2 == 0)); //1_ % 2 == 0を 値 と し て 渡す
static constexpr auto data = sprout::array<int, 5>{{0, 1, 2, 3, 4}};
static_assert(count_even(data) == 3, "");
Return Cast
戻り値キャストです.ret<Type>(expr)
と記述することで,無名関数_expr_の戻り値をType
へキャストして返す無名関数となります.
using veiler::lampads::udl::operator"" _;
using veiler::lampads::udl::operator"" _tail;
using veiler::lampads::if_;
using veiler::lampads::self;
using veiler::lampads::num_of_args;
using veiler::lampads::ret;
static constexpr auto sum = if_(num_of_args == 1ul)[1_].else_[self(1_ + 2_, 2_tail)];
static_assert(ret<int>(sum)(1.5, 2.5, 3.5, 4.5, 5.5) == 17, ""); //sumの結果をdoubleからintに変換
static constexpr auto sum_int = if_(num_of_args == 1ul)[ret<int>(1_)].else_[self(ret<int>(1_) + ret<int>(2_), 2_tail)]; //引数をintに変換してから総和を求める
static_assert(sum_int(1.5, 2.5, 3.5, 4.5, 5.5) == 15, "");
演算子とキャプチャ
Veiler.Lampadsではこれらの基本関数と演算子を組み合わせて任意の無名関数を作ります.演算子を無名関数に適用したものは「『引数で無名関数を評価した結果』に対して演算子を適用する」無名関数となります.
またこの際,無名関数でないもの(定数や変数など)は可能な限り自動的にValueにラップされ,定数関数となります.これにより,可視範囲内の任意の外部変数や一時オブジェクトの値キャプチャ/ムーブキャプチャが可能です.また,constexpr reference_wrapperライブラリVeiler.Refilを用いる(veiler::ref(t)
)ことで,参照キャプチャを行うことも出来ます.また,veiler::move
を用いることでムーブキャプチャも可能です.
引数の評価戦略
Veiler.Lampadsは複数の評価戦略を持ちます.call-by-valueとcall-by-referenceです.
call-by-value
キャプチャと同様に,普通に渡した場合には値渡しとして扱われますが,veiler::move
やveiler::ref
等を用いることでムーブ/参照渡しが可能です.普通のC++の関数に近い引数の取り扱いをします.
call-by-reference
全ての引数をforwardingします.つまり,lvalue referenceを引数に与えればlvalue referenceとして,それ以外の引数は全てmoveして関数を実行します.より高速ですが,C++における引数の扱いとは異なる挙動を示します.
そして,Veiler.Lampadsでは効率を重視して9デフォルトではcall-by-referenceを採用しています.もし評価戦略をcall-by-valueに変更したいのであれば,
#define VEILER_LAMPADS_DEFAULT_EVALUATION_STRATEGY pass::by_value
#include <veiler/lampads.hpp>
とする必要があります.また,一部の呼び出しについてのみ変更したい場合は,
f.call<veiler::lampads::by_value>(args...);
f.call<veiler::lampads::by_reference>(args...);
とすることで好きな評価戦略での呼び出しが可能です.
Polymorphic Accessor
さて,ここでいくつか問題があります.まず,Placeholderを通してメンバにアクセスすることは出来ません.
using veiler::lampads::udl::operator"" _;
struct hoge{
int a, b;
};
(1_.a)(hoge{}); //Error!
無論これでは使い物になりません.これを実現するためには,operator ->*
とメンバポインタを使って間接的なアクセスをすることになります.
using veiler::lampads::udl::operator"" _;
struct hoge{
int a, b;
};
static constexpr hoge h{};
(&1_->*&hoge::a)(h); //OK!
これはBoost.Lambdaにも存在する問題とその解法ですが,これには問題があります.まず,&1_
の部分でhoge
オブジェクトのアドレスを取る必要が有るため,一度オブジェクトを作った上で参照渡しを行わなければなりません(一時オブジェクトはアドレスが取れませんので,この方法は取れません).また,メンバポインタによって1_
は必然的にhoge
(或いはそれを継承した型)のオブジェクトであることが規定されてしまうのです.せっかくの多相関数なのに,台無しです.
using veiler::lampads::udl::operator"" _;
using veiler::lampads::by_value;
struct hoge{
int a, b;
};
struct fuga{
double a;
};
static constexpr auto access_a = &1_->*&hoge::a;
static constexpr fuga f{};
access_a(hoge{}); //Error!
access_a.call<by_value>(veiler::ref(f)); //Error!
これを解決するためには,VEILER_LAMPADS_POLYMORPHIC_MEMBER_ACCESSOR
を使用します.
static constexpr VEILER_LAMPADS_POLYMORPHIC_MEMBER_ACCESSOR(a) a{};
using veiler::lampads::udl::operator""_;
using veiler::lampads::by_value;
struct hoge{
int a, b;
};
struct fuga{
double a;
};
static constexpr auto access_a = 1_->*a;
static constexpr fuga f{};
access_a(hoge{}); //OK!
access_a.call<by_value>(veiler::ref(f)); //OK!
あるオブジェクトをobj
,アクセスしたいメンバの名前をmember
としたとき(即ち,obj.member
へアクセスしたいとき),VEILER_LAMPADS_POLYMORPHIC_MEMBER_ACCESSOR(member)
型のオブジェクトをaccessor
とすると,obj->*accessor
とすることでobj.member
にアクセスすることが出来ます.また,メンバ関数呼出しにも対応しており,この場合無名関数列args...
を用いてobj->*accessor(args...)
とすることで無名関数となります(Bindのような挙動を示します).
static constexpr VEILER_LAMPADS_POLYMORPHIC_MEMBER_ACCESSOR(set) set{};
using veiler::lampads::udl::operator""_;
struct hoge{
int a;
constexpr hoge(int a):a(a){}
int set(int n){return a = n;}
}h(3);
std::cout << h.a << std::endl; //3
(1_ ->* set(2_))(h, 10);
std::cout << h.a << std::endl; //10
また,基本関数のBindの項で示した例のように,template関数をbindするためにはtemplate引数を埋めて特殊化した形にしなければなりません.これもまた,せっかくの多相が無駄になってしまいます.そこで,VEILER_LAMPADS_POLYMORPHIC_FUNCTION_ACCESSOR
の出番です.
static constexpr VEILER_LAMPADS_POLYMORPHIC_FUNCTION_ACCESSOR(sprout::range::count_if) count_if{};
using veiler::lampads::udl::operator"" _;
using veiler::lampads::val;
using veiler::lampads::bind;
static constexpr auto count_even = bind(count_if, 1_, val(1_ % 2 == 0));
static constexpr auto data = sprout::array<int, 5>{{0, 1, 2, 3, 4}};
static_assert(count_even(data) == 3, ""); //count_if<sprout::array<int, 5>, decltype(1_ % 2 == 0)>
static constexpr auto data2 = sprout::array<int, 10>{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}};
static_assert(count_even(data2) == 5, ""); //count_if<sprout::array<int, 10>, decltype(1_ % 2 == 0)>
----- 簡易な物ですが,リファレンスは以上です.いかがでしたでしょうか.もし「試してみたいけどコンパイラが手元に無い」ですとか,「試してみたいけどcloneするのめんどい」ですとか,そういったことでしたら,ぜひ[こちら](http://sc.yutopp.net/entries/54925daa77777707ec654500)のテンプレをご利用下さい. あ な た と 鳥 小 屋 , 今 す ぐ L a m p a d s
実装について
使い方については以上ですが,C++ Advent Calendarを読んでいる皆様のことですから使い方よりライブラリの中身の方が気になることと思います.しかも,実装がめちゃくちゃ汚い10ので読めたものではありませんから,なおさら日本語での解説が必要でしょう.というわけで,実装についても出来る限り書きます.しかし,おそらくある程度の知識(templateやconstexprを用いたC++メタプログラミング(特にtupleを使ったもの),簡単な関数型プログラミング)を前提とした記述になってしまっていると思うので,仮に分からないことがある場合にはTwitterに連絡ください.出来る限り分かりやすく説明させていただきます.また,場合によってはこちらに追記もします.
なお,以下の解説中におけるコードは読みやすさを重視して話題と無関係なコードやtemplate
修飾子・typename
修飾子の省略を行っている場合があります.また,記事掲載当時のコードを元に解説を行うため最新版のVeiler.Lampadsでは動作が異なる場合があります.
概要
Veiler.Lampadsの無名関数はインターフェースとなるテンプレートクラスLampads
と,基本関数の実体となるクラス群によって構成されています.
Lampads
Lampads
は関数オブジェクトとして評価戦略に従った引数授受やresult_type
を定義する,といった役割を担います.また,ライブラリの内部においては無名関数であることの表明として用いられます.ユーザーが実際に操作しているのはLampads
です.
各基本関数実装
基本関数の実態はそれぞれクラスとして実装されており,共通のインターフェースを有しています.Lampads
や相互でインターフェースを叩き合い,計算を行います.Lampads
によって隠蔽されており,ユーザーが直接触れることはありません.
この2つを交互に見ていくことで,Veiler.Lampadsがどのような仕組みで動いているのかを読み解くことができます.それでは,ケーススタディを通して実装を追ってみましょう.
ケース1 : Value
まず,最も単純な例として
val(1)()
について考えてみましょう.無論,この式の期待される結果はint
型の値1
です.さて,val
の定義は以下のようになっています.
template<typename T>
constexpr Lampads<Val<unwrap_refil_t<T>>> val(T&& t){
return Lampads<Val<unwrap_refil_t<T>>>(unwrap_refil_or_copy(veiler::forward<T>(t)));
}
即ち,先述の式は
Lampads<Val<unwrap_refil_t<int>>>(unwrap_refil_or_copy(veiler::forward<int>(1)))()
と同等です.さて,unwrap_refil_t
やunwrap_refil_or_copy
とはなんなのでしょうか.
template<typename T>
constexpr T unwrap_refil_or_copy(T t){return veiler::move(t);}
template<typename T>
constexpr T& unwrap_refil_or_copy(veiler::refil<T> t){return t.get();}
template<typename T>
using unwrap_refil_t = decltype(unwrap_refil_or_copy(std::declval<T>()));
unwrap_refil_or_copy
は引数がveiler::refil<T>
であればそれを剥がして中の参照を取り出し,それ以外であれば受け取った引数のコピーを返す,という関数であり,unwrap_refil_t
はこの操作を行った際の型を返すメタ関数であることがわかりました.int
はveiler::refil<T>
ではありませんから,コピーされることになります11.
Lampads<Val<int>>(1)()
前置きが長くなりましたが,ここからが本題です.まずは,インターフェースクラスのLampads<T>
について見てみましょう.
template<typename T, typename = void>class LampadsImpl;
template<typename T>
class LampadsImpl<T, typename std::enable_if<T::ret_type::depends_on_args == false>::type>{
public:
using result_type = typename T::ret_type::template type<>;
protected:
template<pass, typename = void>struct call_impl;
template<typename Dummy>
struct call_impl<pass::by_value, Dummy>{
template<typename... Args>
static constexpr result_type call(const T& t, Args&&... args){
return veiler::forward<result_type>(t.template run<result_type>(t, unwrap_refil_or_copy(veiler::forward<Args>(args))...));
}
};
template<typename Dummy>
struct call_impl<pass::by_reference, Dummy>{
template<typename... Args>
static constexpr result_type call(const T& t, Args&&... args){
return veiler::forward<result_type>(t.template run<result_type>(t, veiler::forward<unremove_reference_decay<Args>>(args)...));
}
};
};
/*(中略)*/
template<typename T>
class Lampads : public LampadsImpl<T>{
T t;
using impl_t = LampadsImpl<T>;
public:
/*(中略)*/
template<typename X, typename... Args>
explicit constexpr Lampads(X&& x, Args&&... args):t(veiler::forward<X>(x), veiler::forward<Args>(args)...){}
/*(中略)*/
template<typename... Args>
constexpr auto operator()(Args&&... args)const
->decltype(impl_t::template call_impl<VEILER_LAMPADS_DEFAULT_EVALUATION_STRATEGY>::call(t, veiler::forward<Args>(args)...)){
return impl_t::template call_impl<VEILER_LAMPADS_DEFAULT_EVALUATION_STRATEGY>::call(t, veiler::forward<Args>(args)...);
}
/*(中略)*/
};
本題からそれる部分は省略しましたが,これがLampads
の実装となります.さて,今回T
はVal<int>
です.まずはコンストラクタが呼ばれます.
T t;
public:
template<typename X, typename... Args>
explicit constexpr Lampads(X&& x, Args&&... args):t(veiler::forward<X>(x), veiler::forward<Args>(args)...){}
T
型のメンバt
に引数を全てforwardingしているだけです.つまり,Val<int>
に1
を渡しています.それでは次にVal
について見てみましょう.
template<typename T>
class Val{
T t;
public:
struct ret_type:std::true_type{
template<typename...>using type = T;
static constexpr bool depends_on_args = false;
};
template<typename... Args>
constexpr Val(Args&&... args):t(veiler::forward<Args>(args)...){}
template<typename R, typename... Args>
constexpr T run(Args&&...)const{return t;}
/*(中略)*/
};
Val
のコンストラクタはメンバt
に受け取った引数を投げつけます.結局,int t
を1
で初期化するだけです.これでLampads<Val<int>>
型のオブジェクトが構築出来ました.次に,関数呼び出しを行います.Lampads
のoperator()
を見てみましょう.
using impl_t = LampadsImpl<T>;
public:
template<typename... Args>
constexpr auto operator()(Args&&... args)const
->decltype(impl_t::template call_impl<VEILER_LAMPADS_DEFAULT_EVALUATION_STRATEGY>::call(t, veiler::forward<Args>(args)...)){
return impl_t::template call_impl<VEILER_LAMPADS_DEFAULT_EVALUATION_STRATEGY>::call(t, veiler::forward<Args>(args)...);
}
複雑そうに見えますが,大したことはありません.impl_t
はLampadsImpl<T>
ですが,これはLampads<T>
が継承しているクラスです.そのクラスのメンバであるcall_impl<VEILER_LAMPADS_DEFAULT_EVALUATION_STRATEGY>
のメンバ関数call
を呼び出しているだけです.今回引数はありませんし,VEILER_LAMPADS_DEFAULT_EVALUEATION_STARTEGY
はリファレンスで述べた通り,デフォルトではpass::by_reference
となっていますから,operator()
の返す式は
LampadsImpl<Val<1>>::call_impl<pass::by_reference>::call(t)
となります.LampadsImpl<T>
についてみてみると,
public:
using result_type = typename T::ret_type::template type<>;
protected:
template<pass, typename = void>struct call_impl;
template<typename Dummy>
struct call_impl<pass::by_reference, Dummy>{
template<typename... Args>
static constexpr result_type call(const T& t, Args&&... args){
return veiler::forward<result_type>(t.template run<result_type>(t, veiler::forward<unremove_reference_decay<Args>>(args)...));
}
};
とあります.しつこいようですが,今回無引数で呼び出していますので
veiler::forward<result_type>(t.run<result_type>(t))
となります.LampadsImpl<Val<int>>
のメンバとしてresult_type
がVal<int>::ret_type::type<>
のエイリアスとして定義されています.これはVal
に
struct ret_type:std::true_type{
template<typename...>using type = T;
static constexpr bool depends_on_args = false;
};
とあるのでT
,今回で言えばint
となります.従って,
veiler::forward<int>(t.run<int>(t))
です.t
はVal<int>
であることを思い出してください.Val
のメンバ関数run
は以下の様なものでした.
template<typename R, typename... Args>
constexpr T run(Args&&...)const{return t;}
つまり,Val<int>
のメンバint t
を返す関数です.以上より,
veiler::forward<int>(1)
ですから,期待通りint
型の値1
が得られました.
まとめ
このケースからは,以下のようなことを学ぶことが出来ます.
- Valueの実態はテンプレートクラス
Val
である.val
関数を通して,Lampads
に包まれた状態でユーザーに提供される. -
Lampads<T>
のT
はメンバクラスret_type
を持つ必要がある.ret_type::type<Args...>
は引数としてArgs...
が渡された時の戻り値型となる. -
Lampads<T>
のT
はメンバ関数run
を持つ.run
はT
のオブジェクトと引数を渡され,適切な値を返す.
ケース2 : 二項演算
次に,
(val(1) + 1)()
について見て行きましょう.期待される結果はint
の2
です.最初のケースで長々やったので,一度扱ったものは飛ばしていきます.
(Lampads<Val<int>>(1) + 1)()
Lampads
に対して加算しています.演算子がオーバーロードされていますね.以下が呼び出されるoperator +
の定義です.
template<typename T, typename U, typename std::enable_if<is_lampads<typename std::remove_cv<typename std::remove_reference<T>::type>::type>::value ||
is_lampads<typename std::remove_cv<typename std::remove_reference<U>::type>::type>::value>::type* = nullptr>
constexpr Lampads<Add<unwrap_lampads_or_valize_t<T>, unwrap_lampads_or_valize_t<U>>> operator +(T&& t, U&& u){
return Lampads<Add<unwrap_lampads_or_valize_t<T>, unwrap_lampads_or_valize_t<U>>>(unwrap_lampads_or_valize(veiler::forward<T>(t)), unwrap_lampads_or_valize(veiler::forward<U>(u)));
}
長くてごちゃごちゃしてますが,
- 引数は
(T&&, U&&)
-
T``U
少なくとも1つは参照やcv修飾子を外した場合にLampads<T>
となる - 戻り値型は
Lampads<Add<unwrap_lampads_or_valize_t<T>, unwrap_lampads_or_valize_t<U>>>
である - 戻り値は
Lampads<Add<unwrap_lampads_or_valize_t<T>, unwrap_lampads_or_valize_t<U>>>(unwrap_lampads_or_valize(veiler::forward<T>(t)), unwrap_lampads_or_valize(veiler::forward<U>(u)))
である
というだけです.unwrap_lampads_or_valize
とやらが分かれば何とかなりそうですし,これも名前からして想像がつきますよね?
template<typename T>
constexpr Val<unwrap_refil_t<T>> unwrap_lampads_or_valize(T&& t){
return Val<unwrap_refil_t<T>>(unwrap_refil_or_copy(veiler::forward<T>(t)));
}
template<typename T>
constexpr T unwrap_lampads_or_valize(Lampads<T>&& t){
return Lampads<T>::_get(veiler::move(t));
}
template<typename T>
constexpr T unwrap_lampads_or_valize(const Lampads<T>& t){
return Lampads<T>::_get(t);
}
template<typename T>
using unwrap_lampads_or_valize_t = decltype(unwrap_lampads_or_valize(std::declval<T>()));
Lampads<T>
なら中身を取り出し,それ以外ならunwrap_refil_or_copy
してVal
に包む12.簡単ですね.
Lampads<Add<unwrap_lampads_or_valize_t<Lampads<Val<int>>>, unwrap_lampads_or_valize_t<int>>>(unwrap_lampads_or_valize(veiler::forward<Lampads<Val<int>>>(Lampads<Val<int>>(1))), unwrap_lampads_or_valize(veiler::forward<int>(1)))()
これは,
Lampads<Add<Val<int>, Val<int>>>(Val<int>(1), Val<int>(1))()
となります.ここで注目して欲しいのは,加算演算子の右辺にあったint(1)
がVal<int>(1)
に化けたことです.これがリファレンスで言及したキャプチャ機構の正体です.そして,加算の実態はAdd
というクラスが担っていることが分かります.
ところで,言うまでもないことですが殆の二項演算でこのような処理がなされます.つまり,
3.14 * (Val(2) + 5)
の型は
Lampads<Mul<Val<double>, Add<Val<int>, Val<int>>>>
のようになります.式の構造を型に落としこむ,Expression Templateという技法です.詳細はおググりください13.
さて,本題に戻しますとAdd
とはなんぞや,という話です.
template<typename T, typename U>
class Add{
T t;
U u;
struct ret_type_t:std::true_type{
template<typename... Args>
using type = decltype(std::declval<typename T::ret_type::template type<Args...>>() + std::declval<typename U::ret_type::template type<Args...>>());
static constexpr bool depends_on_args = T::ret_type::depends_on_args || U::ret_type::depends_on_args;
};
/*(中略)*/
public:
using ret_type = typename std::conditional<T::ret_type::value && U::ret_type::value, ret_type_t, ret_type_dummy>::type;
template<typename R, typename S, typename... Args>
constexpr auto run(const S& s, Args&&... args)const
->decltype(t.template run<R>(s, veiler::forward<Args>(args)...)
+ u.template run<R>(s, veiler::forward<Args>(args)...)){
return t.template run<R>(s, veiler::forward<Args>(args)...)
+ u.template run<R>(s, veiler::forward<Args>(args)...);
}
/*(中略)*/
constexpr Add(const T& t, const U& u):t(t), u(u) {}
constexpr Add(const T& t, U&& u):t(t), u(veiler::forward<U>(u)){}
constexpr Add( T&& t, const U& u):t(veiler::forward<T>(t)), u(u) {}
constexpr Add( T&& t, U&& u):t(veiler::forward<T>(t)), u(veiler::forward<U>(u)){}
};
コンストラクタによってメンバt
とu
に引数がそれぞれそのまま渡ります.ここではどちらもVal<int>(1)
ですね.ret_type
はstd::conditional
によって条件分岐が成されていますが,Val<int>::ret_type::value
は共にtrue
ですから,ret_type_t
が採用されます.従ってAdd<Val<int>, Val<int>>::ret_type::type<>
は
decltype(std::declval<Val<int>::ret_type::type<>>() + std::declval<Val<int>::ret_type::type<>>())
Val<int>::ret_type::type<>
はケース1でやりましたね.
decltype(std::declval<int>() + std::declval<int>())
つまり「int
とint
を足した結果の型」ですから,int
となります.また,ret_type::depends_on_args
も同様に操作していけばfalse
となります.
肝心のrun
関数は,「t
とu
それぞれのrun
の結果を加算して返す」というものです.Val<int>
のrun
もまた,ケース1でやりましたからもう分かりますね.答えは確かに2
となりました.
まとめ
このケースからは,以下のようなことを学ぶことが出来ます.
- Veiler.Lampadsの無名関数はExpression Templateによって生成される.
- オーバーロードされた二項演算子によって
Lampads
ではないものはVal
に包まれる.これがキャプチャの正体である. - 演算子それぞれに実装の型がある.内部で被演算子の無名関数を呼び出し,その結果に演算子を作用させている.
ケース3 : 引数
お次は,
(1_ + 1)(1)
を考えてみましょう.ついに引数が出てきました.やはり期待される結果は2ですが,果たして.
まずは1_
について見てみなければなりません.この記法はユーザー定義リテラル(user-defined literals,UDL)を用いて実現しています.
template<long long N, char c, char... Chars>
struct udl_to_placeholder{
using type = typename std::enable_if<'0'<=c && c<='9', typename udl_to_placeholder<N*10 + c-'0', Chars...>::type>::type;
};
template<long long N>
struct udl_to_placeholder<N,'\0'>{using type = Lampads<Placeholder<N>>;};
template<char... Chars>
constexpr typename udl_to_placeholder<0, Chars...,'\0'>::type operator""_(){
return typename udl_to_placeholder<0, Chars...,'\0'>::type{};
}
え?分からない?簡単な事です…
N_
は
Lampads<Placeholder<N>>{}
になります(本当にそれだけです,そしてこれ以上の解説はUDLの解説になるので割愛します).なので,先ほどの式は
(Lampads<Placeholder<1>>{} + 1)(1)
です.ケース2より,
Lampads<Add<Placeholder<1>, Val<int>>>({}, Val<int>(1))(1)
となります.それでは,そろそろPlaceholder
について見て行きましょう.
template<long long N>
struct Placeholder{
struct ret_type:std::true_type{
template<typename... Args>
using type = veiler::type_at<veiler::type_tuple<Args...>, N-1>;
static constexpr bool depends_on_args = true;
};
constexpr Placeholder() = default;
template<typename R, typename S, typename... Args>
constexpr auto run(const S&, Args&&... args)const
->veiler::type_at<veiler::type_tuple<Args...>, N-1>{
return veiler::get<N-1>(veiler::forward_as_tuple<Args&&...>(veiler::forward<Args>(args)...));
}
/*(中略)*/
};
run
は「引数を全てveiler::tuple
としてforwarding,その中からN-1
番目の要素を取り出して返す」というコードとなっています.**tupleは便利ですね.**よって,今回の式は
get<1-1>(forward_as_tuple<int&&>(forward<int>(1))) + 1
ですから,
1 + 1
ということで,やはり期待していた通りの答えとなりました.
ところで,Val
と違いPlaceholder
はret_type::depends_on_args
の値がtrue
になっています.Placeholder<1>::ret_type::value
はtrue
なので,Add<Placeholder<1>, Val<int>>::ret_type
はケース2の時と同じくAdd<Placeholder<1>, Val<int>>::ret_type_t
ですが,
struct ret_type_t:std::true_type{
template<typename... Args>
using type = decltype(std::declval<typename T::ret_type::template type<Args...>>() + std::declval<typename U::ret_type::template type<Args...>>());
static constexpr bool depends_on_args = T::ret_type::depends_on_args || U::ret_type::depends_on_args;
};
depends_on_args
はtrue || false
,即ちtrue
となります.これ,実はLampadsImpl<T>
今までのケースとは違うものになってしまうのです.
template<typename T>
class LampadsImpl<T, typename std::enable_if<T::ret_type::depends_on_args == true>::type>{
protected:
template<pass, typename = void>struct call_impl;
/*(中略)*/
template<typename Dummy>
struct call_impl<pass::by_reference, Dummy>{
template<typename... Args>
static constexpr auto call(const T& t, Args&&... args)
->decltype(t.template run<typename T::ret_type::template type<unremove_reference_decay<Args>...>>(t, veiler::forward<unremove_reference_decay<Args>>(args)...)){
return t.template run<typename T::ret_type::template type<unremove_reference_decay<Args>...>>(t, veiler::forward<unremove_reference_decay<Args>>(args)...);
}
};
};
ケース1では触れませんでしたが,LampadsImpl<T>
はT::ret_type::depends_on_args
の値によって特殊化されています.そして,depends_on_args
がtrue
である場合,LampadsImpl<T>::result_type
(率いてはこれを継承しているLampads<T>
のメンバresult_type
)が定義されていないことが分かるでしょうか.そもそも,depends_on_args
,「引数に依存している」という名前を見れば分かるでしょうが,これは「その無名関数の戻り値は引数に依存するか」を表すフラグです.引数に戻り値の型が依存したら,当然result_type
は定義できませんよね.
C++における関数オブジェクトは,(特にC++03時代のライブラリと併用する場合)result_type
が定義されていると何かと都合がいい事が多いです.一方で,多相性を導入するとこれが実現し難い.そのため,Veiler.Lampadsでは無名関数によってresult_type
が定義可能かを自動的に検出し,可能であれば定義する,という方針を取りました.それがこのret_type::depends_on_args
なのです.
まとめ
このケースからは,以下のようなことを学ぶことが出来ます.
- Placeholderの実態はテンプレートクラス
Placeholder
である.operator"" _
を通して,Lampads
に包まれた状態でユーザーに提供される. -
Val
は引数に依存しない.Placeholder
は引数に依存する. - 戻り値型が引数に依存する
Lampads
は,result_type
が定義されない.これはLampadsImpl<T>
の特殊化によって成立する. - tupleは便利である.
ケース4 : キャストと分岐
このペースでやっていると文量が恐ろしく長くなるので,そろそろ2つ一気にやりましょう.
if_(1_)[0].else_[ret<int>(2_)](false, 1.)
Return CastとIf Then Elseです.今回はやや複雑ですが,期待する結果は1
です.まずはret
を展開しましょう.
template<typename R, typename T>
constexpr Lampads<Ret<R, T>> ret(Lampads<T>&& t){return Lampads<Ret<R, T>>{Lampads<T>::_get(veiler::move(t))};}
template<typename R, typename T>
constexpr Lampads<Ret<R, T>> ret(const Lampads<T>& t){return Lampads<Ret<R, T>>{Lampads<T>::_get(t)};}
Lampads<T>
をとって中身をそっくりそのまま渡したLampads<Ret<R, T>>
を返す.簡単ですね.
if_(Lampads<Placeholder<1>>{})[
0
].else_[
Lampads<Ret<int, Placeholder<2>>>{}
](false, 1.)
次はIf Then Elseを解除しましょう.
template<typename C>constexpr If<C> if_(C&& c){return If<C>(veiler::forward<C>(c));}
if_
は関数です.If
型のオブジェクトを生成するようです.
If<Lampads<Placeholder<1>>>(veiler::forward<Lampads<Placeholder<1>>>({}))[
0
].else_[
Lampads<Ret<int, Placeholder<2>>>{}
](false, 1.)
If
の中身はというと,
template<typename C>class If{
C&& c;
public:
constexpr If(C&& cond):c(veiler::forward<C>(cond)){}
template<typename T>constexpr IfElseBridge<C, T> operator[](T&& t)const{return IfElseBridge<C, T>(Else<C, T>(veiler::forward<C>(c), veiler::forward<T>(t)));}
};
渡された参照c
をそのまま保持するコンストラクタと,保持した参照c
及び渡された参照t
でIfElseBridge
型のオブジェクトを構築するoperator[]
があります.
IfElseBridge<Lampads<Placeholder<1>>, int>(
Else<Lampads<Placeholder<1>>, int>(
veiler::forward<Lampads<Placeholder<1>>>({}),
veiler::forward<int>(0)
)
)
.else_[
Lampads<Ret<int, Placeholder<2>>>{}
](false, 1.)
段々ややこしくなってきました.IfElseBridge
は
template<typename C, typename T>struct IfElseBridge{
constexpr IfElseBridge(Else<C,T>&& e):else_(veiler::move(e)){}
Else<C,T> else_;
};
Else<C, T>
を受け取って,publicメンバのelse_
を初期化するだけのようです.publicメンバなので,直接アクセスできます.実質は,
Else<Lampads<Placeholder<1>>, int>(
veiler::forward<Lampads<Placeholder<1>>>({}),
veiler::forward<int>(0)
)[
Lampads<Ret<int, Placeholder<2>>>{}
](false, 1.)
こういうことでしょう.最後に,Else
は
template<typename C, typename T>class Else{
C&& c; T&& t;
public:
constexpr Else(C&& c, T&& t):c(veiler::forward<C>(c)),t(veiler::forward<T>(t)){}
template<typename U>
constexpr Lampads<IfImpl<unwrap_lampads_or_valize_t<C>, unwrap_lampads_or_valize_t<T>, unwrap_lampads_or_valize_t<U>>> operator[](U&& u)const{
return Lampads<IfImpl<unwrap_lampads_or_valize_t<C>, unwrap_lampads_or_valize_t<T>, unwrap_lampads_or_valize_t<U>>>(unwrap_lampads_or_valize(veiler::forward<C>(c)),unwrap_lampads_or_valize(veiler::forward<T>(t)),unwrap_lampads_or_valize(veiler::forward<U>(u)));
}
};
ついにLampads
やunwrap_lampads_or_valize
が登場しました.つまり,これで最後です.コンストラクタにおけるc
やt
の扱いはこれまでと同じなので端折りますが
Lampads<IfImpl<
unwrap_lampads_or_valize_t<Lampads<Placeholder<1>>>,
unwrap_lampads_or_valize_t<int>,
unwrap_lampads_or_valize_t<Lampads<Ret<int, Placeholder<2>>>>
>>(
unwrap_lampads_or_valize(veiler::forward<Lampads<Placeholder<1>>>({})),
unwrap_lampads_or_valize(veiler::forward<int>(0)),
unwrap_lampads_or_valize(veiler::forward<Lampads<Ret<int, Placeholder<2>>>>({})),
)(false, 1.)
これを展開すると
Lampads<IfImpl<
Placeholder<1>,
Val<int>,
Ret<int, Placeholder<2>>
>>(
Placeholder<1>{},
Val<int>(0),
Ret<int, Placeholder<2>>({}),
)(false, 1.)
以上が前置きです.IfImpl
を見てみましょう.
template<typename C, typename T, typename U>class IfImpl{
C c; T t; U u;
struct ret_type_t:std::true_type{
template<typename... Args>
using type = typename _IfImpl_ret_type<T, U>::template type<Args...>;
static constexpr bool depends_on_args = _IfImpl_ret_type<T, U>::depends_on_args;
};
public:
using ret_type = typename std::conditional<T::ret_type::value || U::ret_type::value, ret_type_t, ret_type_dummy>::type;
constexpr IfImpl(C c, T t, U u):c(veiler::forward<C>(c)),t(veiler::forward<T>(t)),u(veiler::forward<U>(u)){}
template<typename R, typename S, typename... Args>
constexpr auto run(const S& s, Args&&... args)const
->typename _IfImpl_ret_type<T, U>::template type<Args...>{
return c.template run<R>(s,veiler::forward<Args>(args)...)
?t.template run<R>(s,veiler::forward<Args>(args)...)
:u.template run<R>(s,veiler::forward<Args>(args)...);
}
/*中略*/
};
コンストラクタはもういいでしょう.run
も誰にでも分かる内容(それぞれのrun
を呼んで条件演算子で分岐するだけ)です.見慣れないものとしては_IfImpl_ret_type
ぐらいのものですが,これは可能な限りコピーを抑えるために,「出来る限り参照を残したT::ret_type::<Args...>
とU::ret_type::type<Args...>
両方から変換可能な型を求める14」メタ関数を物量条件分岐で実現しているサムシングです.載せても無駄に記事が長くなるだけなので,読みたい人はソースのl.583-621をあたってください.ただの力押しです.1つ言っておくとすれば,今回depends_on_args
はfalse
,つまりresult_type
が定義される状態となります.後述しますが,Ret<int, T>::ret_type::depends_on_args
はfalse
です(戻り値の"型"はRet
で固定されるのですから引数には依存しませんよね?).Val
のそれと共にfalse
ですから,IfImpl
もまた戻り値型について引数に依存しません.
再掲しますが,結論としては,以下を求めれば良いと.
Lampads<IfImpl<
Placeholder<1>,
Val<int>,
Ret<int, Placeholder<2>>
>>(
Placeholder<1>{},
Val<int>(0),
Ret<int, Placeholder<2>>({}),
)(false, 1.)
結論から言ってしまえば,第1引数はfalse
ですから,Placeholder<1>{}.run
の返す値はfalse
です.ですから,求めるべきはRet<int, Placeholder<2>>({}).run(s, false, 1.)
です.Ret
は以下のようになっています.
template<typename RetType, typename T>
class Ret{
T t;
public:
struct ret_type:std::true_type{
template<typename...>using type = RetType;
static constexpr bool depends_on_args = false;
};
constexpr Ret(T&& t):t(veiler::forward<T>(t)){}
constexpr Ret(const T& t):t(t){}
template<typename R, typename S, typename... Args>
constexpr RetType run(const S& s, Args&&... args)const{return veiler::forward<RetType>(t.template run<R>(s, veiler::forward<Args>(args)...));}
}
};
Ret<RT, T>::run
はveiler::forward<RT>(T::run)
となります.簡単ですね.以上より,
veiler::forward<int>(1.)
ですから,int
の1
が答えとなります.
まとめ
このケースからは,以下のようなことを学ぶことが出来ます.
- Return Castの実態はテンプレートクラス
Ret
である.ret
を通して,Lampads
に包まれた状態でユーザーに提供される. - If Then Elseの実態はテンプレートクラス
IfImpl
である.if_
やoperator[]
,else_
を通して,最終的にLampads
に包まれた状態でユーザーに提供される. - If Then Elseの戻り値型はそれっぽいものがいい感じに決定される(雑)
- Return Castを使うことで,(その中身がどのような無名関数であろうと)
depends_on_args
をfalse
にすることが出来る.result_type
を定義したいときには都合がいい.
ケース5 : 再帰とVariadic Placeholder
長々だらだらとやって来ましたが,ついにこのケース5でVeiler.Lampadsの(ほぼ)全貌を掴むことが出来ます.
さて,ここまで見てきて殆どの方は感じたことと思いますが,ケース1~4に渡って,あることについてあからさまに誤魔化し続けてきています.ケース1のまとめをもう一度見てみましょう.
まとめ
このケースからは,以下のようなことを学ぶことが出来ます.
- Valueの実態はテンプレートクラス
Val
である.val
関数を通して,Lampads
に包まれた状態でユーザーに提供される.Lampads<T>
のT
はメンバクラスret_type
を持つ必要がある.ret_type::type<Args...>
は引数としてArgs...
が渡された時の戻り値型となる.Lampads<T>
のT
はメンバ関数run
を持つ.run
はT
のオブジェクトと引数を渡され,適切な値を返す.
さて,ではここで質問です.ret_type::type<Args...>
は何のために必要なのでしょうか?result_type
を定義するため,ではありません.「引数に依存しない時のみ定義されるresult_type
のため」であれば,Args...
を受け取る道理がないからです.また,ケース3を見返してみると,LampadsImpl<T>::call_impl<pass::by_reference>::call
内で,t.run
のテンプレート引数としてT::ret_type::type<Args...>
を渡しています.しかし,Add
もIfImpl
もRet
もR
として受け取ってそのまま下のrun
に流しているだけだし,Val
とPlaceholder
は受け取った型R
をそのまま捨てています.
もう1つ質問です.run
はLampads<T>::operator()
が受け取った引数以外にT
のオブジェクトを渡されていますが,これは何故でしょう?先述のR
と同様に,これもまたAdd
・IfImpl
・Ret
はそのまま下に流し,Val
とPlaceholder
は捨てています.
そして最後に1つ.今までのコードの殆どで出てきた/*(中略)*/
には,一体何が書かれているのでしょうか?このように多くのことについて説明を省き続けてきたのは,序盤から出てくる割に仕組みは割と複雑な出力機構に対して起こる疑問「何故std::printfは引数をいくつでも取れるの?」だの「何故std::coutはシフト演算子で出力が出来るの?」だのと同じように,説明をするには前提となる知識が足りなかったからです.今,上述の疑問を解消するためのケースを扱うために必要な道具は揃いました.今回のお題は,以下の式です.
if_(num_of_args == 1ul)[1_].else_[self(1_ + 2_, 2_tail)](1, 2, 3, 4)
今回の初出は3つ.Number Of Args,Variadic Placeholder,そしてSelfです.期待される結果はint
の10
.順番に見て行きましょう.
struct NumOfArgs{
struct ret_type:std::true_type{
template<typename...>
using type = std::size_t;
static constexpr bool depends_on_args = false;
};
constexpr NumOfArgs() = default;
template<typename R, typename S, typename... Args>
constexpr std::size_t run(const S&, Args&&...)const{
return sizeof...(Args);
}
/*(中略)*/
};
namespace{
constexpr Lampads<NumOfArgs> num_of_args{};
}
num_of_args
はLampads<NumOfArgs>
型のオブジェクトそのものです.NumOfArgs::run
の返すものはsizeof...(Args)
.戻り値型はstd::size_t
固定なので引数に依存しません.
template<long long N, char c, char... Chars>
struct udl_to_variadic_placeholder{
using type = typename std::enable_if<'0'<=c && c<='9', typename udl_to_variadic_placeholder<N*10 + c-'0', Chars...>::type>::type;
};
template<long long N>
struct udl_to_variadic_placeholder<N,'\0'>{using type = Lampads<VariadicPlaceholder<N>>;};
template<char... Chars>
constexpr typename udl_to_variadic_placeholder<0, Chars...,'\0'>::type operator""_tail(){
return typename udl_to_variadic_placeholder<0, Chars...,'\0'>::type{};
}
続いて,operator"" _tail
ですが,operator"" _
の時と殆ど変わりませんね.2_tail
はLampads<VariadicPlaceholder<2>>{}
になります.
template<typename... Params>
constexpr Lampads<Self<unwrap_lampads_or_valize_t<Params>...>> self(Params&&... ps){
return Lampads<Self<unwrap_lampads_or_valize_t<Params>...>>(unwrap_lampads_or_valize(veiler::forward<Params>(ps))...);
}
そしてself
です.引数を全てunwrap_lampads_or_valize
してSelf
のコンストラクタに渡す,というコードです.つまり,あの式は
Lampads<IfImpl<
Eq<
NumOfArgs,
Val<unsigned long>
>,
Placeholder<1>,
Self<Add<Placeholder<1>, Placeholder<2>>, VariadicPlaceholder<2>>
>>(
Eq<NumOfArgs,Val<unsigned long>>{
NumOfArgs{},
Val<unsigned long>(1ul)
},
Placeholder<1>{},
Self<Add<Placeholder<1>, Placeholder<2>>, VariadicPlaceholder<2>>{
Add<Placeholder<1>, Placeholder<2>>{
Placeholder<1>{},
Placeholder<2>{}
},
VariadicPlaceholder<2>{}
}
)(1, 2, 3, 4)
となります.長いと思うかもしれませんが,下半分の型名を省略すれば
Lampads<IfImpl<
Eq<
NumOfArgs,
Val<unsigned long>
>,
Placeholder<1>,
Self<Add<Placeholder<1>, Placeholder<2>>, VariadicPlaceholder<2>>
>>({{},1ul},{},{{{},{}},{}})(1, 2, 3, 4)
となりますから,中身は殆どありません.
さて,今回は改めてLampads<T>::call_impl<pass::by_reference>::call
の呼び出しからやり直してみましょう.まず,LampadsImpl<T>
はどちらが選ばれるのでしょうか.ret_type::depends_on_args
がカギ,でしたね.IfImpl::ret_type
は
struct ret_type_t:std::true_type{
template<typename... Args>
using type = typename _IfImpl_ret_type<T, U>::template type<Args...>;
static constexpr bool depends_on_args = _IfImpl_ret_type<T, U>::depends_on_args;
};
public:
using ret_type = typename std::conditional<T::ret_type::value || U::ret_type::value, ret_type_t, ret_type_dummy>::type;
ここで,T
はPlaceholder<1>
ですから,少なくともT::ret_type::value
はtrue
です.よってret_type_t
を参照します._IfImpl_ret_type<T, U>
はT::ret_type
とU::ret_type
によって分岐していますので,ここで一度Self
のret_type
を調べておきましょう.以下にSelf
を示します.
template<typename... Params>
class Self{
veiler::tuple<Params...> params;
template<typename R, long long... Indices, typename S, typename Tuple>
constexpr R run_impl_impl(veiler::index_tuple<Indices...>, const S& s, Tuple&& tpl)const{
return veiler::forward<R>(s.template run<R>(s, veiler::get<Indices>(veiler::forward<Tuple>(tpl))...));
}
template<typename R, long long... Indices, typename S, typename... Args>
constexpr R run_impl(veiler::index_tuple<Indices...>, const S& s, Args&&... args)const{
return run_impl_impl<R>(
veiler::make_index_range<0,
tuple_size<decltype(veiler::tuple_cat(veiler::get<Indices>(params).template bind_run<R>(s, veiler::forward<Args>(args)...)...))>{}
>{},
s,
veiler::forward<decltype(veiler::tuple_cat(veiler::get<Indices>(params).template bind_run<R>(s, veiler::forward<Args>(args)...)...))>(
veiler::tuple_cat(veiler::get<Indices>(params).template bind_run<R>(s, veiler::forward<Args>(args)...)...))
);
}
public:
using ret_type = ret_type_dummy;
constexpr Self(Params&&... ps):params(veiler::forward<Params>(ps)...){}
template<typename R, typename S, typename... Args>
constexpr R run(const S& s, Args&&... args)const{return run_impl<R>(veiler::make_indexes<Params...>{}, s, veiler::forward<Args>(args)...);}
/*(中略)*/
};
ret_type
がret_type_dummy
にaliasされていますね.これは何回か出てきてはいましたが紹介はまだでした.
struct ret_type_dummy:std::false_type{
template<typename...>using type = void;
static constexpr bool depends_on_args = true;
};
戻り値の型type<Args...>
がvoid
なのも目を引きますが,これまではずっとtrue
であったvalue
がfalse
になっています.これは「意味のあるret_typeが定義できない」場合に使うダミーなのです.そして,_IfImpl_ret_type
で今回の場合に合致するのは,以下の特殊化です.
template<typename T, typename U>
struct _IfImpl_ret_type<T, U, typename std::enable_if< T::ret_type::value && !U::ret_type::value>::type>{
template<typename... Args>
using type = typename T::ret_type::template type<Args...>;
static constexpr bool depends_on_args = T::ret_type::depends_on_args;
};
U::ret_type::value
がfalse
である場合,_IfImpl_ret_type
はT::ret_type
に準ずる,ということです.つまり,IfImpl::ret_type
はPlaceholder<1>::ret_type
と等しい,ということになります.従って,ケース3を思い出せばよいですね.
template<typename T>
class LampadsImpl<T, typename std::enable_if<T::ret_type::depends_on_args == true>::type>{
protected:
template<pass, typename = void>struct call_impl;
/*(中略)*/
template<typename Dummy>
struct call_impl<pass::by_reference, Dummy>{
template<typename... Args>
static constexpr auto call(const T& t, Args&&... args)
->decltype(t.template run<typename T::ret_type::template type<unremove_reference_decay<Args>...>>(t, veiler::forward<unremove_reference_decay<Args>>(args)...)){
return t.template run<typename T::ret_type::template type<unremove_reference_decay<Args>...>>(t, veiler::forward<unremove_reference_decay<Args>>(args)...);
}
};
};
ケース3では全く触れませんでしたが,unremove_reference_decay<T>
とは,
template<typename T,
typename U = typename std::remove_reference<T>::type,
bool = std::is_array<U>::value || std::is_function<U>::value>
struct unremove_reference_decay_impl;
template<typename T, typename U>
struct unremove_reference_decay_impl<T, U, true>{using type = typename std::decay<U>::type;};
template<typename T, typename U>
struct unremove_reference_decay_impl<T, U, false>{using type = T;};
template<typename T>
using unremove_reference_decay = typename unremove_reference_decay_impl<T>::type;
配列や関数を直接受け取った場合,適切なポインタに置き換えるためのメタ関数です(std::decay
では参照が外れてしまいますが,unremove_reference_decay
は配列や関数でなければ参照を保持します).今回は配列や関数は引数に存在しないので無視して構いません.
Placeholder<1>::ret_type::type<int, int, int, int>
は
int
ですから,
/*tを
IfImpl<
Eq<
NumOfArgs,
Val<unsigned long>
>,
Placeholder<1>,
Self<Add<Placeholder<1>, Placeholder<2>>, VariadicPlaceholder<2>>
>({{},1ul},{},{{{},{}},{}}) とするなら*/
t.run<int>(t, 1, 2, 3, 4)
即ち,
Eq<NumOfArgs, Val<unsigned long>>{{}, 1ul}.run<int>(t, 1, 2, 3, 4) ?
Placeholder<1>{}.run<int>(t, 1, 2, 3, 4) :
Self<Add<Placeholder<1>, Placeholder<2>>, VariadicPlaceholder<2>>{{{},{}},{}}.run<int>(t, 1, 2, 3, 4);
さて,引数の数は4ul
ですから,1ul
とは等しくありません.従って,この式は
Self<Add<Placeholder<1>, Placeholder<2>>, VariadicPlaceholder<2>>{{{},{}},{}}.run<int>(t, 1, 2, 3, 4);
となります.Self
のコンストラクタは,
veiler::tuple<Params...> params;
constexpr Self(Params&&... ps):params(veiler::forward<Params>(ps)...){}
とありますから,paramsにAdd<Placeholder<1>, Placeholder<2>>{}, VariadicPlaceholder<2>{}
が入ります.そして,run
は実質run_impl
への委譲のみです.run_impl
までを展開しましょう.
run_impl_impl<int>(
veiler::make_index_range<0,
tuple_size<decltype(veiler::tuple_cat(veiler::get<index>(params).template bind_run<int>(t, 1, 2, 3, 4)...))>{}
>{},
t,
veiler::forward<decltype(veiler::tuple_cat(veiler::get<Indices>(params).template bind_run<int>(t, 1, 2, 3, 4)...))>(
veiler::tuple_cat(veiler::get<Indices>(params).template bind_run<int>(t, 1, 2, 3, 4)...)
)
)
index
はrun
の時に渡されたindex_tuple
の中身で,Params...
から生成されているので今回は0, 1
です.従って,
run_impl_impl<int>(
veiler::make_index_range<0,
tuple_size<decltype(veiler::tuple_cat(veiler::get<0>(params).bind_run<int>(t, 1, 2, 3, 4),
veiler::get<1>(params).bind_run<int>(t, 1, 2, 3, 4)))>{}
>{},
t,
veiler::forward<decltype(veiler::tuple_cat(veiler::get<0>(params).bind_run<int>(t, 1, 2, 3, 4),
veiler::get<1>(params).bind_run<int>(t, 1, 2, 3, 4)))>(
veiler::tuple_cat(veiler::get<0>(params).bind_run<int>(t, 1, 2, 3, 4),
veiler::get<1>(params).bind_run<int>(t, 1, 2, 3, 4))
)
)
ですから,params
の中身を取り出してしまえば
run_impl_impl<int>(
veiler::make_index_range<0,
tuple_size<decltype(veiler::tuple_cat(Add<Placeholder<1>, Placeholder<2>>{}.bind_run<int>(t, 1, 2, 3, 4),
VariadicPlaceholder<2>{}.bind_run<int>(t, 1, 2, 3, 4)))>{}
>{},
t,
veiler::forward<decltype(veiler::tuple_cat(Add<Placeholder<1>, Placeholder<2>>{}.bind_run<int>(t, 1, 2, 3, 4),
VariadicPlaceholder<2>{}.bind_run<int>(t, 1, 2, 3, 4)))>(
veiler::tuple_cat(Add<Placeholder<1>, Placeholder<2>>{}.bind_run<int>(t, 1, 2, 3, 4),
VariadicPlaceholder<2>{}.bind_run<int>(t, 1, 2, 3, 4))
)
)
さて,bind_run
という見かけないメンバ関数が呼ばれています,これが多くの15/*(中略)*/
の正体です.これは大雑把に言ってしまえばstd::make_tuple(run(args...))
に相当する処理を行っています.何故tupleに包むのかは,以下のコードによって説明できます.
template<long long N>
struct VariadicPlaceholder{
static_assert(N >= 0, "Veiler.Lampads - VariadicPlaceholder can't be negative.");
private:
template<long long... Indices, typename... Args>
constexpr auto run_impl(veiler::index_tuple<Indices...>, Args&&... args)const
->veiler::tuple<veiler::type_at<veiler::type_tuple<Args...>, Indices>...>{
return veiler::make_tuple(wrap_refil_if_ref(veiler::get<Indices>(veiler::forward_as_tuple<Args&&...>(veiler::forward<Args>(args)...)))...);
}
public:
using ret_type = ret_type_dummy;
constexpr VariadicPlaceholder() = default;
template<typename R, typename S, typename... Args>
constexpr R run(const S&, Args&&...)const; //Veiler.Lampads - can't use variadic placeholder except in self and bind.
template<typename R, typename S, typename... Args, typename std::enable_if<N < sizeof...(Args)>::type* = nullptr>
constexpr auto bind_run(const S&, Args&&... args)const
->decltype(this->run_impl(veiler::make_index_range<N, sizeof...(Args)>{}, veiler::forward<Args>(args)...)){
return this->run_impl(veiler::make_index_range<N, sizeof...(Args)>{}, veiler::forward<Args>(args)...);
}
template<typename R, typename S, typename... Args, typename std::enable_if<(N >= sizeof...(Args))>::type* = nullptr>
constexpr auto bind_run(const S&, Args&&...)const
->veiler::tuple<>{
return veiler::make_tuple();
}
};
VariadicPlaceholder<N>
,0-indexedで第N
引数より後ろ全てを表すプレースホルダ.これを実現するためには,
-
Self
側でVariadicPlacholder<N>
を受け取った時にだけ特殊な操作を実現するか16, - 複数の値を返す
必要があります.そう,「複数の値」です.Veiler.Lampadsでは「tuple
で返す」という方法を選択しました.VariadicPlaceholder<N>::bind_run(t, args...)
は,第N
引数以降の引数全てが入ったtuple
を返します.
これで繋がりました.何故tuple
で包むだけのbind_run
が各実装に入り込んでいるのか.答えは,Variadic Placeholderを実装するためだったのです.それでは,式を展開します.
run_impl_impl<int>(
veiler::make_index_range<0,
tuple_size<decltype(veiler::tuple_cat(make_tuple(1 + 2), make_tuple(3, 4)))>{}
>{},
t,
veiler::forward<decltype(veiler::tuple_cat(make_tuple(1 + 2), make_tuple(3, 4)))>(
veiler::tuple_cat(make_tuple(1 + 2), make_tuple(3, 4))
)
)
tuple_cat
は,引数の全てのtuple
の中身を展開,再統合する関数ですので,
run_impl_impl<int>(
veiler::make_index_range<0,
tuple_size<decltype(make_tuple(1 + 2, 3, 4))>{}
>{},
t,
veiler::forward<decltype(make_tuple(1 + 2, 3, 4))>(
make_tuple(1 + 2, 3, 4)
)
)
加算とforwardingを展開して
run_impl_impl<int>(
veiler::make_index_range<0,
tuple_size<decltype(make_tuple(3, 3, 4))>{}
>{},
t,
make_tuple(3, 3, 4)
)
tuple_size<T<Types...>>
はsizeof...(Types)
を得るためのメタ関数ですから,
run_impl_impl<int>(
veiler::make_index_range<0, 3>{},
t,
make_tuple(3, 3, 4)
)
となります.run_impl_impl
は
template<typename R, long long... Indices, typename S, typename Tuple>
constexpr R run_impl_impl(veiler::index_tuple<Indices...>, const S& s, Tuple&& tpl)const{
return veiler::forward<R>(s.template run<R>(s, veiler::get<Indices>(veiler::forward<Tuple>(tpl))...));
}
ですから,展開すると
veiler::forward<int>(t.template run<int>(t, 3, 3, 4))
ここで,t
は元々Lampads<T>
のメンバですから,再帰が実現しています.2・3度繰り返せば,10
が得られるでしょう.
さて,それでは話を最初に戻しましょう.何故ret_type::type<Args...>
や,run
の先頭にt
を渡す必要があったのか,です.結論から言えば共に「Self
で使うから」で,後者は明らかにSelf
で使っていますが,前者は説明の中ではあまり必要性が感じられなかったのではないでしょうか.先述のSelf
のコードを見ると,run
,run_impl
,run_impl_impl
全ての戻り値型がR
となっています.何故decltype
による推論を行わないのでしょうか.簡単な話で,「無限再帰に陥るから」です.decltype(t.run(~~))
とすると,t.run(~~)
の中でt.run(~~)
が出てきてしまいますから,式の形が定まりません.となると,decltype
のしようがありません.従って,Self
にはt
のみならず,その戻り値型も渡さなければならないのです.しかも,Self
の戻り値型は分からない状態で,なんとか戻り値型を取得せねばなりません.この「求められないところは無視しながら分かる範囲で戻り値型を推論する仕組み」こそがret_type::type<Args...>
というわけです.そして,「戻り値型が求められない」ことを示すのがret_type_dummy
.Self
とSelf
を含む式,そしてIfImpl
のThen(T
)とElse(U
)双方のret_type
がret_type_dummy
だった場合がこれに該当します.
ときに,「名前の無い関数の再帰」というのは,関数型プログラミングやラムダ計算に触れたことのない人にとってはなんとも不思議な感じはしないでしょうか.再帰呼び出しするなら,名前が必要なはずですよね.実は「名前の無い関数の再帰」は無名再帰と呼ばれるのですが,有名な無名再帰の手法の1つに「不動点コンビネータ」というものがあります.詳細はおググりいただくとして,具体的には以下の様なコードにおけるfix
が不動点コンビネータとなります.
auto fact_impl = [](auto self, int x){
return x == 0 ? 1 : x * self(x - 1);
};
auto fact = fix(fact_impl);
fact(4) //4 * 3 * 2 * 1 * 1 = 24
fix
は関数fact_impl
を受け取ると,fact_impl
の第1引数をfact_impl
として部分適用した新たな関数を返します.当然,残りの引数x
を与えてやればfact_impl(fact_impl, x)
として呼び出されますから,見事再帰が実現できました,という寸法です.気づきましたでしょうか?Lampads<T>
はT
に対する不動点コンビネータなのです.そして,「隠された第0引数」にアクセスするための方法こそが,Selfなのです.
まとめ
このケースからは,以下のようなことを学ぶことが出来ます.
- Number Of Argsの実態はクラス
NumOfArgs
である.Lampads
に包まれた状態のnum_of_args
がユーザーに提供される. - Selfの実態はテンプレートクラス
Self
である.self
を通して,Lampads
に包まれた状態でユーザーに提供される. - Variadic Placeholderの実態はテンプレートクラス
VariadicPlaceholder
である.operator"" _tail
を通して,Lampads
に包まれた状態でユーザーに提供される. -
ret_type::type<Args...>
はSelf
の戻り値型を決定するためのメタ関数である. -
bind_run
関数はVariadic Templateを実現するために存在する,tuple
を返す関数である. -
run
やbind_run
の第1引数に渡されるt
はSelf
のために渡される.Lampads<T>
はT
の不動点コンビネータとしての役割を持つ. - tupleは便利である.17
おまけ
ケース5によって,Veiler.Lampadsの全貌が見えました.Lampads
や各無名関数実装に深く入り込んだ小汚いハックによって,SelfとVariadic Placholderは成り立っているのです.さて,それでは未解説内容について適当に流していきましょう.ここまで読んできた方なら,もうLampadsを直接読むことも不可能ではないはずです.
Bind
using has_result_type = VEILER_HASTUR_TAG_CREATE(result_type);
template<bool, typename, typename...>class Bind;
template<typename F, typename... Params>
class Bind<true, F, Params...>{
F f;
veiler::tuple<Params...> params;
template<typename R, long long... Indices, typename S, typename... Args>
constexpr auto run_impl(veiler::index_tuple<Indices...>, const S& s, Args&&... args)const
->decltype(veiler::apply(f, veiler::tuple_cat(veiler::get<Indices>(params).template bind_run<R>(s, veiler::forward<Args>(args)...)...))){
return veiler::apply(f, veiler::tuple_cat(veiler::get<Indices>(params).template bind_run<R>(s, veiler::forward<Args>(args)...)...));
}
public:
struct ret_type:std::true_type{
template<typename...>
using type = typename veiler::func_troy<F>::result_type;
static constexpr bool depends_on_args = false;
};
constexpr Bind(F&& f, Params&&... params):f(veiler::forward<F>(f)), params(veiler::forward<Params>(params)...){}
template<typename R, typename S, typename... Args>
constexpr auto run(const S& s, Args&&... args)const
->decltype(this->run_impl<R>(veiler::make_indexes<Params...>{}, s, veiler::forward<Args>(args)...)){
return this->run_impl<R>(veiler::make_indexes<Params...>{}, s, veiler::forward<Args>(args)...);
}
template<typename R, typename S, typename... Args>
constexpr auto bind_run(const S& s, Args&&... args)const
->decltype(this->run_impl<R>(veiler::make_indexes<Params...>{}, s, veiler::forward<Args>(args)...)){
return this->run_impl<R>(veiler::make_indexes<Params...>{}, s, veiler::forward<Args>(args)...);
}
};
template<typename F, typename... Params>
class Bind<false, F, Params...>{
F f;
veiler::tuple<Params...> params;
template<typename R, long long... Indices, typename S, typename... Args>
constexpr auto run_impl(veiler::index_tuple<Indices...>, const S& s, Args&&... args)const
->decltype(veiler::apply(f, veiler::tuple_cat(veiler::get<Indices>(params).template bind_run<R>(s, veiler::forward<Args>(args)...)...))){
return veiler::apply(f, veiler::tuple_cat(veiler::get<Indices>(params).template bind_run<R>(s, veiler::forward<Args>(args)...)...));
}
public:
using ret_type = ret_type_dummy;
constexpr Bind(F&& f, Params&&... params):f(veiler::forward<F>(f)), params(veiler::forward<Params>(params)...){}
template<typename R, typename S, typename... Args>
constexpr auto run(const S& s, Args&&... args)const
->decltype(this->run_impl<R>(veiler::make_indexes<Params...>{}, s, veiler::forward<Args>(args)...)){
return this->run_impl<R>(veiler::make_indexes<Params...>{}, s, veiler::forward<Args>(args)...);
}
template<typename R, typename S, typename... Args>
constexpr auto bind_run(const S& s, Args&&... args)const
->decltype(this->run_impl<R>(veiler::make_indexes<Params...>{}, s, veiler::forward<Args>(args)...)){
return this->run_impl<R>(veiler::make_indexes<Params...>{}, s, veiler::forward<Args>(args)...);
}
};
template<typename F, typename... Params>
constexpr Lampads<Bind<veiler::hastur<has_result_type>::type<veiler::func_troy<F>>::value, unremove_reference_decay<F>, unwrap_lampads_or_valize_t<Params>...>> bind(F&& f, Params&&... ps){
return Lampads<Bind<veiler::hastur<has_result_type>::type<veiler::func_troy<F>>::value, unremove_reference_decay<F>, unwrap_lampads_or_valize_t<Params>...>>(veiler::forward<F>(f), unwrap_lampads_or_valize(veiler::forward<Params>(ps))...);
}
Bindの実態Bind
は,「部分適用する関数F
にresult_type
が存在するか否か」で特殊化されています.具体的には,
- 関数ポインタ
-
result_type
が定義された関数オブジェクト
のいずれかであれば,Bind<F, ...>
はdepends_on_args
がfalse
になります.先述の通り,Veiler.Lampadsは出来る限りresult_type
を定義します.あとはおおよそSelf
と同じ動作です.bind_run
という名前を考えれば当然ですよね?
call-by-value
さて,call-by-referenceでの解説を行ってきましたが,call-by-valueではどうなるのでしょうか.これは,LampadsImpl<T>::call_impl<pass::by_value>::call
を見れば分かります.
template<typename Dummy>
struct call_impl<pass::by_value, Dummy>{
template<typename... Args>
static constexpr auto call(const T& t, Args&&... args)
->decltype(t.template run<typename T::ret_type::template type<unwrap_refil_t<Args>...>>(t, unwrap_refil_or_copy(veiler::forward<Args>(args))...)){
return t.template run<typename T::ret_type::template type<unwrap_refil_t<Args>...>>(t, unwrap_refil_or_copy(veiler::forward<Args>(args))...);
}
};
簡単ですね.veiler::refil<T>
ならT&
に,それ以外はコピーする.そのままです.これはdepends_on_args
がtrue
の場合ですが,false
の場合でも大した差はありません.
Polymorphic Accessor
VEILER_LAMPADS_POLYMORPHIC_MEMBER_ACCESSOR
やVEILER_LAMPADS_POLYMORPHIC_FUNCTION_ACCESSOR
自体は,実はLampads
とは何の関わりもないクラスなのです.特に後者は,引数をforwardingするだけのラッパクラスです.また前者は,operator->*
をオーバーロードしてa ->* member_accessor
の結果をa.member
にしているだけです.ですから,Veiler.Lampadsとは関わりのない文脈でも正しく動作します.ただし,member_accessorのoperator()
に関しては都合が変わります.これはリファレンスに
Bindのような挙動を示します
とある通り,Lampads
を返すのです.そして,戻り値の型がoperator->*
をさらにオーバーロードしている…という構図です.まぁ,Self
が読めれば読めるでしょう.
Clangのバグへの対処
ここまで解説を行ってきましたが,ここで1つ伝えておかなければならないことがあります.それは,「本解説中のコードはGCC用である」ということです.そう,Veiler.LampadsはGCCとClangでコードが違うのです.実際ファイルを開いてみればわかると思いますが,VEILER_LAMPADS_RECURSION_*
というマクロが随所に書かれているはずです.これらは,GCCでは全て単に消えます.というのも,Clangのバグに対処するためのworkaroundだからです.
Clangには「相互再帰する constexpr 関数テンプレートを実体化すると、テンプレート実体化が無限再帰してコンパイルエラーになる」という致命的なバグがあります.つまり,Self
のrun
を使ったら再帰深度超過でコンパイルできなくなるということです.これを避けるために,Clangでビルドした時のみ再帰深度の限界を規定しておき,全ての関数呼び出しをカウントして,Self
(と一応Bind
)のrun
で「もう1周関数呼び出しできるか」を確認,不可能な程度に再帰カウンタが増えていたら関数呼び出しをダミーに差し替える,というworkaroundを挟んであります.Veiler.Lampads全体における小汚いハックがSelfを実現させているのです.
謝辞
当ライブラリを開発するにあたって,多くの人に助けてもらいました.
「constexprなラムダライブラリは作れる!」という雑談をした後,当ライブラリの原型となる物を一晩で作って見せてくれたphi16(@phi16_),昨年のtupleの制作も元はといえばこれのため.私に膨大なモチベーションを与えてくれました.また,(1_ % 2 == 0)(3) == true
の一件18でも大いにお世話になりました.
Variadic PlaceholderやPlaceholder UDLなどはボレロ村上(@bolero_MURAKAMI)氏のアイデアを 丸パクリ 参考にさせていただきました.またconstexpr周りのバグは,大抵調べたら氏のブログやスライドが出てくるため,いつもいつも参考にさせて頂いております.
大変便利な開発環境・ProcGarden(通称 : 鳥小屋)を提供してくれたぶんちょう(@yutopp)さん.11月入ってからのLampadsのデバッグはほぼ鳥小屋だけでやりました.大変便利.鳥小屋コントリビュータとして今後共頑張ります.
週26コマの講義による多忙を言い訳にTemple/Deus/Lampadsといったライブラリ群の公開をだらだらと先延ばしにしていた頃に,定数オーダーgetかつコンパイル時間爆速なtupleを一晩で作り上げ,私のやる気スイッチを押してくれたのじ(@fimbul11)さん.日々刺激を受けております.
去年のAdCでconstexpr-Lambda書くかも~って言ったら期待を寄せてくれた(覚えがあるものの,ツイートは探しきれず…)すいかばー(@suibaka)さん.1年遅れたけどちゃんと書きましたよ.
そして,私のTwitterのTLにいらっしゃるC++erの皆様.Veiler.Lampadsは皆様のツイートの産物みたいなものです.
皆様に多大な感謝を.
明日は@yohhoyさんです.
-
「あい」です. ↩
-
実に4週間前から準備を始めたのですが…間に合わないものですねぇ… ↩
-
ネタが有れば,ですが. ↩
-
具体的には,ICE / constexpr関数の呼び出しをコンパイル時定数と認識できない / 後で出てくるサンプルコードに対して「奇数の数は0個だ」と返す,などで,**これらはC++11(
-std=c++11
)でも同様に発症していました.**3日か4日か前に直ったみたいです. ↩ -
Clangを使えよ,という声もあるかもしれませんが,Clangのバグに苦しめられた身としては若干手を出したくなくなってきます…尤も,C++14でwhile/forを使えば再帰深度周りのバグも踏まないのでいっそC++14 with Clangに逃げてもいいかなぁとも思うのですが.しかしそれでも大学の鯖で何故か(カーネルが古いせい?)libc++が動かないのでどっちみちlibstdc++が仕事をしないうちはC++11を使っていくしか無いという悲しい現実.とりあえずGCCとMSVCには頑張って頂きたく.なお,一部からはこのような声も挙がっており,私の望みが無謀・無茶であるのやもしれません. ↩
-
Veiler.Lampadsは
_n
の形式のプレースホルダは提供しません.UDLがなんらかの理由で使用できない(e.g. 他のライブラリのものと被る)ためにn_
が使えない,といった場合にはconstexpr veiler::lampads::arg_t<N> _n{};
といったように,自分でveiler::lampads::arg_t<N>
型のオブジェクトを定義して使ってください.また,UDLに対応していないコンパイラではそもそもVeiler.Lampadsを使用することは出来ません.そんな古びたコンパイラは今すぐ窓から投げ捨ててください.↩ -
Veiler.Lampadsは(ry
veiler::lampads::arg_tail_t<N>
を使用してください. ↩ -
無論,call-by-valueに対しても常に
veiler::ref
などを用いて適切に呼び出せば(最適化がかかるはずなので)call-by-referenceと遜色ない速度は出ます.問題は,例えば上述のsprout::range::count_ifのような関数の引数として渡した際,呼び出し側ではveiler::ref
による修飾が行われないということはほぼ確実であり,そうした際に一々コピーコストが発生するようではパフォーマンスに差支えがあるため,このような方針となっています. ↩ -
汚いのは依存関係の都合で定義が飛び散っている(e.g. operator overloadできる場所が限られている都合で
operator[]
とoperator=
の実装がLampads<T>
より上部に来ている,それらの実装のためにis_lampads
やVal
などの定義がさらにその上に来ている,しかし特殊化is_lampads<Lampads<T>>
やらLampads<Val<T>> val(T&&)
やらはLampads<T>
の下に来なければならない)こと,Variadic Placeholderを実現するために似たようなコード(run
とbind_run
)が各無名関数実装毎に存在すること,Clangのためのworkaroundがいたるところに入り込んでいること,decltype
による戻り値型の記述で式が毎回重複すること,などが原因です.つまり,一部の式がやたらめったら横に伸びている以外は基本的に「回避できない/しにくい実装上の都合」です. ↩ -
といっても,実際には
unwrap_refil_or_copy(veiler::forward<int>(1))
という呼び出しの通りforwardingされますし,この場合はムーブされますのでコピーコストはかかりません(スカラー型なので最適化が働いて置換される…はず). ↩ -
「Val化」を「valize」と表現した筆者の貧弱英語力に笑ったそこのアナタ,より良い名前を見つけてpull-reqしてくださいね. ↩
-
一応軽く触れておくと,元々は「コンパイル時に求まる型情報を使って遅延評価を実現すれば,実行時には計算すること増えないし一時オブジェクトを生成せずに計算できるからパフォーマンスアップだ!」みたいな技法です(3日目の西山さんが紹介なさっているEigenも採用しています,「高速(テンプレートが展開され、余計な変数が生成されない)」という記述がETのこと).Boost.LambdaやVeiler.Lampadsといった「式が関数になるタイプの無名関数ライブラリ」もまた,「引数を与えられた時に実際の計算を行う」という遅延評価を行っている,と捉えると何故ETを使っているのか分かりやすいのではないでしょうか. ↩
-
e.g.
if_(cond)[1_].else_[2_](a, b)
a
もb
も同じ型の変数である場合,参照が返ります. ↩ -
Val
やPlaceholder
等のLampads<T>
のT
に相当するクラスにおける/*(中略)*/
がbind_run
に関わるものです.Lampads
やLampadsImpl
における/*(中略)*/
はまた別です. ↩ -
ちゃんと読んだわけではないですが多分SproutのBindに対しての可変長プレースホルダの実装はこれだと思います.プレースホルダ側が特に機能を持っていなかったので.あ,全く触れてませんでしたが,元々Variadic Placeholderの元ネタはこちらです. ↩
-
ここまで多用すると,やはり昨年constexprな対数オーダーtupleを実装しておいて本当に良かったなぁと思います.まぁ,今では 対数オーダーなんて 時代遅れだったりするのですが… ↩
-
戻り値型の推論をした後,全ての無名関数実装の呼び出しに最上位の型を適用する,というトンデモ実装になっていた頃があり,バグを解消するために相談したことがありました.今でいうところの
(ret<bool>(1_) % ret<bool>(val(2)) == ret<bool>(val(0)))(3)
と同じ挙動をしたわけです(1_
にint
を与えれば,式全体の戻り値型はint
同士の比較なのでbool
となります.それが各要素に伝播していって件のような形となり,当然bool(3)
(1
)をbool(2)
(1
)で割った余りは0
なので,true
となります). ↩