LoginSignup
18
18

More than 5 years have passed since last update.

Veiler.Lampads - constexpr-Lambda Library

Last updated at Posted at 2014-12-20

この記事はC++ Advent Calendar 2014の20日目の記事です.AdCのリンクは記事の下にあります.


 I(@wx257osn2)です1去年は残念な感じだったので今年こそはちゃんとした記事を書きたかったのですが,今年も間に合いませんでした…2.ところで去年の記事を見返してみて気付いたのですが,去年も20日担当だったんですね.来年も20日にしようかしら3

tl;dr

  1. C++11 constexprな無名関数ライブラリを作ったのでリファレンスと実装解説を行います.
  2. 鳥小屋 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∈{mZ : 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∈{mZ : 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]と記述します.(condexpr1及びexpr2はLampadsの無名関数)与えられた引数を用いて条件関数condを実行し,戻り値がtruefalseかによって関数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::moveveiler::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するのめんどい」ですとか,そういったことでしたら,ぜひこちらのテンプレをご利用下さい.
 あ な た と 鳥 小 屋 ,
今 す ぐ 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_tunwrap_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はこの操作を行った際の型を返すメタ関数であることがわかりました.intveiler::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の実装となります.さて,今回TVal<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 t1で初期化するだけです.これでLampads<Val<int>>型のオブジェクトが構築出来ました.次に,関数呼び出しを行います.Lampadsoperator()を見てみましょう.

  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_tLampadsImpl<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_typeVal<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))

です.tVal<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が得られました.

まとめ

 このケースからは,以下のようなことを学ぶことが出来ます.

  1. Valueの実態はテンプレートクラスValである.val関数を通して,Lampadsに包まれた状態でユーザーに提供される.
  2. Lampads<T>Tはメンバクラスret_typeを持つ必要がある.ret_type::type<Args...>は引数としてArgs...が渡された時の戻り値型となる.
  3. Lampads<T>Tはメンバ関数runを持つ.runTのオブジェクトと引数を渡され,適切な値を返す.

ケース2 : 二項演算

 次に,

(val(1) + 1)()

について見て行きましょう.期待される結果はint2です.最初のケースで長々やったので,一度扱ったものは飛ばしていきます.

(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)));
}

長くてごちゃごちゃしてますが,

  1. 引数は(T&&, U&&)
  2. TU少なくとも1つは参照やcv修飾子を外した場合にLampads<T>となる
  3. 戻り値型はLampads<Add<unwrap_lampads_or_valize_t<T>, unwrap_lampads_or_valize_t<U>>>である
  4. 戻り値は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)){}
};

コンストラクタによってメンバtuに引数がそれぞれそのまま渡ります.ここではどちらもVal<int>(1)ですね.ret_typestd::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>())

つまり「intintを足した結果の型」ですから,intとなります.また,ret_type::depends_on_argsも同様に操作していけばfalseとなります.
肝心のrun関数は,「tuそれぞれのrunの結果を加算して返す」というものです.Val<int>runもまた,ケース1でやりましたからもう分かりますね.答えは確かに2となりました.

まとめ

 このケースからは,以下のようなことを学ぶことが出来ます.

  1. Veiler.Lampadsの無名関数はExpression Templateによって生成される.
  2. オーバーロードされた二項演算子によってLampadsではないものはValに包まれる.これがキャプチャの正体である.
  3. 演算子それぞれに実装の型がある.内部で被演算子の無名関数を呼び出し,その結果に演算子を作用させている.

ケース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と違いPlaceholderret_type::depends_on_argsの値がtrueになっています.Placeholder<1>::ret_type::valuetrueなので,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_argstrue || 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_argstrueである場合,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なのです.

まとめ

 このケースからは,以下のようなことを学ぶことが出来ます.

  1. Placeholderの実態はテンプレートクラスPlaceholderである.operator"" _を通して,Lampadsに包まれた状態でユーザーに提供される.
  2. Valは引数に依存しない.Placeholderは引数に依存する.
  3. 戻り値型が引数に依存するLampadsは,result_typeが定義されない.これはLampadsImpl<T>の特殊化によって成立する.
  4. 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及び渡された参照tIfElseBridge型のオブジェクトを構築する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)));
  }
};

ついにLampadsunwrap_lampads_or_valizeが登場しました.つまり,これで最後です.コンストラクタにおけるctの扱いはこれまでと同じなので端折りますが

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_argsfalse,つまりresult_typeが定義される状態となります.後述しますが,Ret<int, T>::ret_type::depends_on_argsfalseです(戻り値の"型"は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>::runveiler::forward<RT>(T::run)となります.簡単ですね.以上より,

veiler::forward<int>(1.)

ですから,int1が答えとなります.

まとめ

 このケースからは,以下のようなことを学ぶことが出来ます.

  1. Return Castの実態はテンプレートクラスRetである.retを通して,Lampadsに包まれた状態でユーザーに提供される.
  2. If Then Elseの実態はテンプレートクラスIfImplである.if_operator[]else_を通して,最終的にLampadsに包まれた状態でユーザーに提供される.
  3. If Then Elseの戻り値型はそれっぽいものがいい感じに決定される(雑)
  4. Return Castを使うことで,(その中身がどのような無名関数であろうと)depends_on_argsfalseにすることが出来る.result_typeを定義したいときには都合がいい.

ケース5 : 再帰とVariadic Placeholder

 長々だらだらとやって来ましたが,ついにこのケース5でVeiler.Lampadsの(ほぼ)全貌を掴むことが出来ます.
 さて,ここまで見てきて殆どの方は感じたことと思いますが,ケース1~4に渡って,あることについてあからさまに誤魔化し続けてきています.ケース1のまとめをもう一度見てみましょう.

まとめ

 このケースからは,以下のようなことを学ぶことが出来ます.

  1. Valueの実態はテンプレートクラスValである.val関数を通して,Lampadsに包まれた状態でユーザーに提供される.
  2. Lampads<T>Tはメンバクラスret_typeを持つ必要がある.ret_type::type<Args...>は引数としてArgs...が渡された時の戻り値型となる.
  3. Lampads<T>Tはメンバ関数runを持つ.runTのオブジェクトと引数を渡され,適切な値を返す.

さて,ではここで質問です.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...>を渡しています.しかし,AddIfImplRetRとして受け取ってそのまま下のrunに流しているだけだし,ValPlaceholderは受け取った型Rをそのまま捨てています.
 もう1つ質問です.runLampads<T>::operator()が受け取った引数以外にTのオブジェクトを渡されていますが,これは何故でしょう?先述のRと同様に,これもまたAddIfImplRetはそのまま下に流し,ValPlaceholderは捨てています.
 そして最後に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です.期待される結果はint10.順番に見て行きましょう.

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_argsLampads<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_tailLampads<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;

ここで,TPlaceholder<1>ですから,少なくともT::ret_type::valuetrueです.よってret_type_tを参照します._IfImpl_ret_type<T, U>T::ret_typeU::ret_typeによって分岐していますので,ここで一度Selfret_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_typeret_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であったvaluefalseになっています.これは「意味のある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::valuefalseである場合,_IfImpl_ret_typeT::ret_typeに準ずる,ということです.つまり,IfImpl::ret_typePlaceholder<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)...)
  )
)

indexrunの時に渡された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引数より後ろ全てを表すプレースホルダ.これを実現するためには,

  1. Self側でVariadicPlacholder<N>を受け取った時にだけ特殊な操作を実現するか16
  2. 複数の値を返す

必要があります.そう,「複数の値」です.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のコードを見ると,runrun_implrun_impl_impl全ての戻り値型がRとなっています.何故decltypeによる推論を行わないのでしょうか.簡単な話で,「無限再帰に陥るから」です.decltype(t.run(~~))とすると,t.run(~~)の中でt.run(~~)が出てきてしまいますから,式の形が定まりません.となると,decltypeのしようがありません.従って,Selfにはtのみならず,その戻り値型も渡さなければならないのです.しかも,Selfの戻り値型は分からない状態で,なんとか戻り値型を取得せねばなりません.この「求められないところは無視しながら分かる範囲で戻り値型を推論する仕組み」こそがret_type::type<Args...>というわけです.そして,「戻り値型が求められない」ことを示すのがret_type_dummySelfSelfを含む式,そしてIfImplのThen(T)とElse(U)双方のret_typeret_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なのです.

まとめ

 このケースからは,以下のようなことを学ぶことが出来ます.

  1. Number Of Argsの実態はクラスNumOfArgsである.Lampadsに包まれた状態のnum_of_argsがユーザーに提供される.
  2. Selfの実態はテンプレートクラスSelfである.selfを通して,Lampadsに包まれた状態でユーザーに提供される.
  3. Variadic Placeholderの実態はテンプレートクラスVariadicPlaceholderである.operator"" _tailを通して,Lampadsに包まれた状態でユーザーに提供される.
  4. ret_type::type<Args...>Selfの戻り値型を決定するためのメタ関数である.
  5. bind_run関数はVariadic Templateを実現するために存在する,tupleを返す関数である.
  6. runbind_runの第1引数に渡されるtSelfのために渡される.Lampads<T>Tの不動点コンビネータとしての役割を持つ.
  7. 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は,「部分適用する関数Fresult_typeが存在するか否か」で特殊化されています.具体的には,

  1. 関数ポインタ
  2. result_typeが定義された関数オブジェクト

のいずれかであれば,Bind<F, ...>depends_on_argsfalseになります.先述の通り,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_argstrueの場合ですが,falseの場合でも大した差はありません.

Polymorphic Accessor

 VEILER_LAMPADS_POLYMORPHIC_MEMBER_ACCESSORVEILER_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 関数テンプレートを実体化すると、テンプレート実体化が無限再帰してコンパイルエラーになる」という致命的なバグがあります.つまり,Selfrunを使ったら再帰深度超過でコンパイルできなくなるということです.これを避けるために,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さんです.



  1. 「あい」です. 

  2. 実に4週間前から準備を始めたのですが…間に合わないものですねぇ… 

  3. ネタが有れば,ですが. 

  4. 具体的には,ICE / constexpr関数の呼び出しをコンパイル時定数と認識できない / 後で出てくるサンプルコードに対して「奇数の数は0個だ」と返す,などで,これらはC++11(-std=c++11)でも同様に発症していました.3日か4日か前に直ったみたいです. 

  5. Clangを使えよ,という声もあるかもしれませんが,Clangのバグに苦しめられた身としては若干手を出したくなくなってきます…尤も,C++14でwhile/forを使えば再帰深度周りのバグも踏まないのでいっそC++14 with Clangに逃げてもいいかなぁとも思うのですが.しかしそれでも大学の鯖で何故か(カーネルが古いせい?)libc++が動かないのでどっちみちlibstdc++が仕事をしないうちはC++11を使っていくしか無いという悲しい現実.とりあえずGCCとMSVCには頑張って頂きたく.なお,一部からはこのような声も挙がっており,私の望みが無謀・無茶であるのやもしれません. 

  6. 私はC++03とBoostをよく知りません. 

  7. Veiler.Lampadsは_nの形式のプレースホルダは提供しません.UDLがなんらかの理由で使用できない(e.g. 他のライブラリのものと被る)ためにn_が使えない,といった場合にはconstexpr veiler::lampads::arg_t<N> _n{};といったように,自分でveiler::lampads::arg_t<N>型のオブジェクトを定義して使ってください.また,UDLに対応していないコンパイラではそもそもVeiler.Lampadsを使用することは出来ません. そんな古びたコンパイラは今すぐ窓から投げ捨ててください. 

  8. Veiler.Lampadsは(ry   veiler::lampads::arg_tail_t<N>を使用してください. 

  9. 無論,call-by-valueに対しても常にveiler::refなどを用いて適切に呼び出せば(最適化がかかるはずなので)call-by-referenceと遜色ない速度は出ます.問題は,例えば上述のsprout::range::count_ifのような関数の引数として渡した際,呼び出し側ではveiler::refによる修飾が行われないということはほぼ確実であり,そうした際に一々コピーコストが発生するようではパフォーマンスに差支えがあるため,このような方針となっています. 

  10. 汚いのは依存関係の都合で定義が飛び散っている(e.g. operator overloadできる場所が限られている都合でoperator[]operator=の実装がLampads<T>より上部に来ている,それらの実装のためにis_lampadsValなどの定義がさらにその上に来ている,しかし特殊化is_lampads<Lampads<T>>やらLampads<Val<T>> val(T&&)やらはLampads<T>の下に来なければならない)こと,Variadic Placeholderを実現するために似たようなコード(runbind_run)が各無名関数実装毎に存在すること,Clangのためのworkaroundがいたるところに入り込んでいること,decltypeによる戻り値型の記述で式が毎回重複すること,などが原因です.つまり,一部の式がやたらめったら横に伸びている以外は基本的に「回避できない/しにくい実装上の都合」です. 

  11. といっても,実際にはunwrap_refil_or_copy(veiler::forward<int>(1))という呼び出しの通りforwardingされますし,この場合はムーブされますのでコピーコストはかかりません(スカラー型なので最適化が働いて置換される…はず). 

  12. 「Val化」を「valize」と表現した筆者の貧弱英語力に笑ったそこのアナタ,より良い名前を見つけてpull-reqしてくださいね. 

  13. 一応軽く触れておくと,元々は「コンパイル時に求まる型情報を使って遅延評価を実現すれば,実行時には計算すること増えないし一時オブジェクトを生成せずに計算できるからパフォーマンスアップだ!」みたいな技法です(3日目の西山さんが紹介なさっているEigenも採用しています,「高速(テンプレートが展開され、余計な変数が生成されない)」という記述がETのこと).Boost.LambdaやVeiler.Lampadsといった「式が関数になるタイプの無名関数ライブラリ」もまた,「引数を与えられた時に実際の計算を行う」という遅延評価を行っている,と捉えると何故ETを使っているのか分かりやすいのではないでしょうか. 

  14. e.g. if_(cond)[1_].else_[2_](a, b) abも同じ型の変数である場合,参照が返ります. 

  15. ValPlaceholder等のLampads<T>Tに相当するクラスにおける/*(中略)*/bind_runに関わるものです.LampadsLampadsImplにおける/*(中略)*/はまた別です. 

  16. ちゃんと読んだわけではないですが多分SproutのBindに対しての可変長プレースホルダの実装はこれだと思います.プレースホルダ側が特に機能を持っていなかったので.あ,全く触れてませんでしたが,元々Variadic Placeholderの元ネタはこちらです. 

  17. ここまで多用すると,やはり昨年constexprな対数オーダーtupleを実装しておいて本当に良かったなぁと思います.まぁ,今では 対数オーダーなんて 時代遅れだったりするのですが… 

  18. 戻り値型の推論をした後,全ての無名関数実装の呼び出しに最上位の型を適用する,というトンデモ実装になっていた頃があり,バグを解消するために相談したことがありました.今でいうところの(ret<bool>(1_) % ret<bool>(val(2)) == ret<bool>(val(0)))(3)と同じ挙動をしたわけです(1_intを与えれば,式全体の戻り値型はint同士の比較なのでboolとなります.それが各要素に伝播していって件のような形となり,当然bool(3)(1)をbool(2)(1)で割った余りは0なので,trueとなります). 

18
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
18