Skip to content
This repository was archived by the owner on Feb 18, 2023. It is now read-only.

Function Utility

Cranberries edited this page Nov 1, 2017 · 15 revisions

Cranberries.FuncUtil

  • 関数の合成
  • 関数のカリー化
  • 関数に引数を渡す演算子と補助クラス群

の3つが含まれるライブラリ. 個別にインクルードすることもできるが, cranberries/func_util.hppをインクルードすればすべて含まれる.

関数の合成

cranberries/func_util/composition.hpp

クラス

  • composited<F,G>

宣言:

  template < class F, class G >
  class composited;

コンストラクタ:

    constexpr composited(const F& f, const G& g) noexcept
    constexpr composited(F&& f, const G& g) noexcept
    constexpr composited(const F& f, G&& g) noexcept
    constexpr composited(F&& f, G&& g) noexcept
    constexpr composited() noexcept

メンバ関数:

decltype(auto) operator()(Args...)

Function Call.
合成関数に引数を渡して評価した結果を返す.
( F(G(Args...))を実行する ).

    template < class... Args >
    constexpr decltype(auto) operator()(Args&&... args) &
    template < class... Args >
    constexpr decltype(auto) operator()(Args&&... args) const &
    template < class... Args >
    constexpr decltype(auto) operator()(Args&&... args) &&
    template < class... Args >
    constexpr decltype(auto) operator()(Args&&... args) const &&

関数

  • composition(関数1, 関数2, 関数3, ... , 関数N)

宣言:

template < class F, class G >
constexpr
composited<std::decay_t<F>, std::decay_t<G>>
composition(F&& f, G&& g) noexcept;

template < class F, class G, class... Rest >
constexpr
auto
composition(F&& f, G&& g, Rest&&... rest) noexcept;

作用:

関数1(関数2(関数3(...)))のような合成関数をつくる.

例:

int f(int a) { return a + 1; }
int g(int a) { return a * a; }
auto h = cranberries::composition(g, f);

h(2); // g(f(2)) と同じ

関数のカリー化

cranberries/func_util/curry.hpp

クラス

  • curried<Fn>

定義:

  template<typename Fn>
  class curried

コンストラクタ:

    constexpr curried(const Fn& fn) noexcept;
    constexpr curried(Fn&& fn) noexcept;
    constexpr curried() noexcept;

メンバ関数:

decltype(auto) operator()(Args...)

カリー化された関数に引数を部分適用をした関数を返す.
引数なしで呼ばれたときに関数を評価する.

    template<typename ...Args>
    constexpr decltype(auto) operator()(Args&& ...args) & noexcept;
    template<typename ...Args>
    constexpr decltype(auto) operator()(Args&& ...args) && noexcept;
    template<typename ...Args>
    constexpr decltype(auto) operator()(Args&& ...args) const& noexcept;
    template<typename ...Args>
    constexpr decltype(auto) operator()(Args&& ...args) const&& noexcept;
  • curried_<N, Fn>

定義:

  template<size_t N, typename Fn>
  class curried_

コンストラクタ:

    constexpr curried(const Fn& fn) noexcept;
    constexpr curried(Fn&& fn) noexcept;
    constexpr curried() noexcept;

メンバ関数:

decltype(auto) operator()(Args...)

カリー化された関数に引数を部分適用をした関数を返す.
N個目の部分適用されたときに関数を評価する.

    template<typename ...Args>
    constexpr decltype(auto) operator()(Args&& ...args) & noexcept;
    template<typename ...Args>
    constexpr decltype(auto) operator()(Args&& ...args) && noexcept;
    template<typename ...Args>
    constexpr decltype(auto) operator()(Args&& ...args) const& noexcept;
    template<typename ...Args>
    constexpr decltype(auto) operator()(Args&& ...args) const&& noexcept;

関数

  • curry(関数)

宣言:

  template < class F >
  inline constexpr
  curried<std::decay_t<F>>
  curry(F&& f) noexcept;

作用:
カリー化された関数を得る.
クラスcurriedは遅延評価戦略なので, 引数を適用したあとに引数なしでoperator()を呼ぶことで評価される.

例:

struct Sum 
{
    template < class Head, class... Tail >
    constexpr auto operator()(Head&& head, Tail&&... tail) const{
        auto result = head;
        (void)std::initializer_list<int>{ (void( result += tail),0)... };
        return result;
    }
};
    constexpr auto sum = Sum{};
  
    // 可変長引数関数のカリー化
    constexpr auto curried_sum = cranberries::curry(sum);
    
    // 最後に引数なしでoperator()を呼ばないと評価されない
    static_assert(curried_sum(1)(2)(3)(4)(5)() == 15, "");
    // 一度に複数の引数を渡せるようにもした(カリー化とはいったい...)
    static_assert(curried_sum(1, 2, 3)(4)(5)() == 15, "");
  • curry_<N>( 関数 )

作用:
渡せる引数の上限が決まっているカリー化された関数を得る.
N個の引数を渡すと評価される.

例:

struct Sum 
{
    template < class Head, class... Tail >
    constexpr auto operator()(Head&& head, Tail&&... tail) const{
        auto result = head;
        (void)std::initializer_list<int>{ (void( result += tail),0)... };
        return result;
    }
};

constexpr int test(int,int,int) { return 5; }


    // 引数を固定したカリー化, N個目の引数が適用されると自動で戻り値が返ってくる
    constexpr int value = cranberries::curry_<4>(sum)(1)(2)(3)(4);
    static_assert(value==10, "");
    // 関数ポインタから自動で引数の数を推論できる
    static_assert(cranberries::curry_(test)(0)(0)(0)==5, "");

関数への引数の適用

関数に引数を(いろいろな方法で)適用するための演算子オーバーロードと補助クラス群が含まれる.
これらは cranberries::func_util 名前空間に存在する.

このライブラリは黒魔術と言ってよい実装になっている + MSVCとそれ以外で宣言が異なるので宣言・定義など省略する.

演算子

All Apply 演算子 <<=

decltype(auto) operator <<= ( F, Tuple )
decltype(auto) operator <<= ( F, A )

作用:

f <<= tuple
としたとき, tupleの要素をすべて f 渡して f を呼び出す.
C++17(Cranberries.Utilityにもあるけど)のapply(f, tuple)と同様の作用を持つ.

単一要素の場合, tupleでなくても良い.

f <<= 2

のようにできる.


Each Apply 演算子 |=

decltype(auto) operator |= ( F, Tuple )

作用:

f |= tuple

としたとき, tupleの要素を一つづつ f に適用して実行する.
戻り値 f のすべての呼び出し結果が void でないなら tuple で返る.
一つでも void を返す f のオーバーロードがあれば 戻り値は void になる.

補助クラス

operator |= に渡すことで様々な条件で関数に引数を適用できるようになる.
共通してstatic関数bindがあり, これを呼び出して値をbindするとoperator |= に渡すことができるプロキシクラスが生成される.

chunk<N>

f |= chunk<N>::bind(args...)

とすると args... をN個ずつ f に渡すことができる.
f は sizeof...(args)/N 回呼ばれ, 末尾のあまりは読み捨てられる.
f , args... が共にコンパイル時定数のとき, コンパイル時計算可能.

例:

int main() {
  auto println = [](auto&& head, auto&&... tail) {
    std::cout << head;
    (void)cranberries::Swallows{ (void(std::cout << " " << tail),0)... };
    std::cout << std::endl;
  };
  auto add = [](auto _1, auto_2) { return _1 + _2; }
  
  using namespace cranberries::func_util;
  
  println <<= add |= chunk<2>::bind(1, 2, 3, 4, 5, 6, 7); // 3 7 11
  
  /* [ Note: 
    add |= chunk<2>::apply(1, 2, 3, 4, 5, 6, 7)
    returns tuple like bellow
    [add(1,2), add(3,4), add(5,6)] => [3, 7, 11]
    and 7 is discarded.
    
    - end note]*/
}

adjacent<N>

f |= adjacent<N>::bind(args...)

とすると args... を隣接N個ずつ f に渡すことができる.
f は sizeof...(args)-N+1 回呼ばれる.
Nより sizeof...(args) が少ないbindの呼び出しはstatic_assertになる.
f , args... が共にコンパイル時定数のとき, コンパイル時計算可能.

例:

int main() {
  auto println = [](auto&& head, auto&&... tail) {
    std::cout << head;
    (void)cranberries::Swallows{ (void(std::cout << " " << tail),0)... };
    std::cout << std::endl;
  };
  auto add = [](auto _1, auto_2) { return _1 + _2; }
  
  using namespace cranberries::func_util;
  
  println <<= add |= adjacent<2>::bind(1, 2, 3, 4, 5); // 3 5 7 9
  
  /* [ Note: 
    add |= chunk<2>::apply(1, 2, 3, 4, 5)
    returns tuple like bellow
    [add(1,2), add(2,3), add(3,4), add(4,5)] => [3, 5, 7, 9]
    
    - end note]*/
}

permutation<R>

f |= permutaion<R>::bind(args...)

とすると args... からR個選ぶ順列をすべて f に適用してくれる.
N = sizeof...(args) としたとき,
f は N! / (N-R)! 回呼ばれる.
Rはフォルト(permutaion<>)でNになるようになっている.
Rより sizeof...(args) が少ないbindの呼び出しはstatic_assertになる.
args... がすべて同じ型でない場合もstatic_assertになる.
args... は比較可能でなければならず, ソート済みでなければならない.
イテレータのスワップを行うためコンパイル時計算不可能.

例:

int main() {
  auto println = [](auto&& head, auto&&... tail) {
    std::cout << head;
    (void)cranberries::Swallows{ (void(std::cout << " " << tail),0)... };
    std::cout << std::endl;
  };

  println |= permutation<2>::bind(1, 2, 3);
  // 1 2
  // 1 3
  // 2 1
  // 2 3
  // 3 1
  // 3 2
}

combination<R>

f |= combination<R>::bind(args...)

とすると args... からR個選ぶ組み合わせをすべて f に適用してくれる.
N = sizeof...(args) としたとき,
f は N! / (R!(N-R)!) 回呼ばれる.
Rはデフォルト(permutaion<>)でNになるようになっている.
Rより sizeof...(args) が少ないbindの呼び出しはstatic_assertになる.
args... がすべて同じ型でない場合もstatic_assertになる.
args... は比較可能でなければならず, ソート済みでなければならない.
イテレータのスワップを行うためコンパイル時計算不可能.

例:

int main() {
  auto println = [](auto&& head, auto&&... tail) {
    std::cout << head;
    (void)cranberries::Swallows{ (void(std::cout << " " << tail),0)... };
    std::cout << std::endl;
  };

  println |= combination<2>::bind(1, 2, 3);
  // 1 2
  // 1 3
  // 2 3
}

chunk<N, randomized<Times>>

f |= chunk<N, randomized<Times>>::bind(args...)

とすると args... からN個をランダムに選んでTimes回 f に渡すことができる.
f は Times 回呼ばれる.
内部でstd::mt19937が状態を持つので, コンパイル時計算不可能.

例:

int main() {
  auto println = [](auto&& head, auto&&... tail) {
    std::cout << head;
    (void)cranberries::Swallows{ (void(std::cout << " " << tail),0)... };
    std::cout << std::endl;
  };
  auto add = [](auto _1, auto _2) { return _1 + _2; }
  
  using namespace cranberries::func_util;
  
  // (一例)
  // 5 7 4 8 7 6 3 10 8 6 
  println <<= add |= chunk<2, randomized<10>>::bind(1, 2, 3, 4, 5, 6, 7);
}

Clone this wiki locally