C++と色々

主にC++やプログラムに関する記事を投稿します。

Boost.HOFについて

Boost 1.67.0から Boost.HOF というライブラリが追加されました。 HOFはHigher Order Functions(高階関数)の略で、関数や関数オブジェクトのユーティリティを提供するライブラリです。 ヘッダーオンリーでC++11以上の環境で使えます。 3つのコンポーネントが含まれています。

  • Function Adaptors and Decorators: 既存の関数を強化するアダプターやデコレーターです。
  • Functions: 特定の目的のための関数です。
  • Utilities: 関数定義や使用時に使える汎用的なユーティリティです。

この記事では一部の機能を紹介します。

Function Adapters and Decorators

Funcition Adaptors

Function Adaptersは関数を受け取り、その関数をもとに新しい関数を返します。

lazy

lazyはstd::bindのように使うことが出来るアダプターです。std::bindと異なるのはconstexprでも利用することが出来る点です。 サンプルはaddの第2引数を3で束縛した関数を返します。

#include <boost/hof.hpp>
namespace hof = boost::hof;

constexpr int add(int a, int b) {
  return a + b;
}

int main() {
  constexpr auto addThree = hof::lazy(add)(hof::_1, 3);
  static_assert(addThree(5) == 8);
}

pipable

pipableは f(x, args...) のような関数を x | f(args...) の形で呼べるようにします。C++では operator.オーバーロードできませんが、 フリー関数をメンバ関数のようにするイメージです。 ( イメージ x(f) -> x.f() ) メソッドチェーンがスッキリします。 ( g(h(f(x))) のような呼び出しが x | f() | h() | g() のように変形できます) サンプルはadded左辺がaddの第1引数になります。

#include <boost/hof.hpp>
namespace hof = boost::hof;

constexpr int add(int a, int b) {
  return a + b;
}

int main() {
  constexpr auto added = hof::pipable(add);
  constexpr auto result = 3 | added(5) | added(7);
  static_assert(result == 15);
}

flow

flowは複数の関数を受け取りそれらの関数を順番に呼び出す関数を返します。1つ前の関数の戻り値が次の関数の引数になります。使い方によってはpipableと似たようなことができます。 サンプルは引数で渡された文字列をhtmlタグで囲んでいます。

#include <boost/hof.hpp>
#include <iostream>
#include <string>
namespace hof = boost::hof;

std::string addpre(std::string const& str) {
  return "<pre>" + str + "</pre>";
}

std::string adddiv(std::string const& str) {
  return "<div>" + str + "</div>";
}

int main() {
  auto wrap = hof::flow(addpre, adddiv, adddiv);
  std::cout << wrap("<span>aaa</span>") << std::endl;
}

出力

<div><div><pre><span>aaa</span></pre></div></div>

infix

infixは2つ引数を取る関数を接中辞(中間記法とも)に書けるようにします。a + bの+のような二項演算子演算子の位置のイメージです。具体的には f(x, y)x <f> y の形式で呼べるようにします。 サンプルでは std::equal_to を中間記法で呼べるようにします。

#include <boost/hof.hpp>
#include <functional>
namespace hof = boost::hof;

int main() {
  constexpr auto equals = hof::infix(std::equal_to<>{});
  constexpr auto a = 42;
  constexpr auto b = 42;
  static_assert(a <equals> b);
}

proj

projはprojの第2引数に渡した関数を呼び出すときに、各引数に第1引数で渡した関数を呼び出してから渡します。 proj(p, f)(1, 2, 3) == f(p(1), p(2), p(3)) のようなイメージです。

#include <boost/hof.hpp>
#include <iostream>
namespace hof = boost::hof;

int twice(int x) {
  return x * 2;
}

void print(int x, int y, int z) {
  std::cout << x << " " << y << " " << z;
}

int main() {
  constexpr auto twiced_print = hof::proj(twice, print);
  twiced_print(5, 7, 11);
}

出力

10 14 22

Decorators

Decoratorsは関数を受け取り、Function Adapterを返す関数です。

repeat

repeatは指定された回数だけ、関数呼び出しをネストします。 サンプルでは初期値1から2倍にする関数を10回呼び出します。

#include <boost/hof.hpp>
namespace hof = boost::hof;

constexpr int twice(int x) {
  return x * 2;
}

int main() {
  constexpr auto pow_10 = hof::repeat(10)(twice);
  static_assert(pow_10(1) == 1024);
}

if_

if_は第1引数に述語メタ関数を取り、それがtrue_typeの場合は第2引数で渡された関数を呼び出す関数を返します。false_typeの場合、関数呼び出しを行うとコンパイルエラーになるようにします。 サンプルのコメントアウトを外すとコンパイルエラーになります。trueの場合はtwiceがそのまま呼び出されます。

#include <boost/hof.hpp>
#include <type_traits>
namespace hof = boost::hof;

constexpr int twice(int x) {
  return x * 2;
}

int main() {
  hof::if_(std::bool_constant<true>{})(twice)(1);
  // hof::if_(std::bool_constant<false>{})(twice)(1);
}

Functions and Utilities

その他の便利関数を紹介します

always

alwaysは引数にとった値を返す関数を返します。この関数は任意の引数を取ることができますが戻り値は固定です。 サンプルは常に1を返す関数を作成しています。

#include <boost/hof.hpp>
namespace hof = boost::hof;

int main() {
  constexpr auto one = hof::always(1);
  static_assert(one() == 1);
  static_assert(one(1, 3.14, "hello") == 1);
}

identity

identityは引数で渡された値をそのまま返す関数です。

#include <boost/hof.hpp>
namespace hof = boost::hof;

int main() {
  static_assert(hof::identity(42) == 42);
}

construct

constructはテンプレートパラメータで渡された型のインスタンスを作成するファクトリ関数を返します。その関数の引数の値をコンストラクタの引数に渡します。 型だけでなくテンプレートテンプレートパラメータを渡すこともでき、その場合ファクトリ関数の引数からテンプレートパラメータが決まります。 サンプルではX型とクラステンプレートYのインスタンスを作成しています。

#include <boost/hof.hpp>
namespace hof = boost::hof;

struct X {
  X(int, double, char const*) {}
};

template <typename T, typename U, typename V>
struct Y {
  Y(T, U, V) {}
};

int main() {
  const auto createX = hof::construct<X>();
  createX(1, 3.14, "hello");

  const auto createY = hof::construct<Y>();
  createY(true, nullptr, 2.f);
}

BOOST_HOF_LIFT

BOOST_HOF_LIFTは関数テンプレートや関数オブジェクトテンプレートやオーバーロード関数をジェネリックラムダにラップします。通常、関数テンプレートは他の高階関数に渡す場合、型を明示してインスタンス化しなければなりませんし、オーバーロード関数も同様にどのオーバーロードを渡すべきかキャストする必要があります。

イメージ例

std::vector<int> v;

があるとして、関数テンプレートの場合、

template <typename T>
T my_add(T a, T b);
...
std::accumulate(v.begin(), v,end(), 0, &my_sum<int>);

オーバーロード関数の場合

int my_add(int a, int b);
double my_add(double a, double b);
std::string my_add(std::string a, std::string b);
...
std::accumulate(v.begin(), v.end(), static_cast<int (*)(int, int)>(my_add));

のようなことが必要です。BOOST_HOF_LIFTはジェネリックラムダでラップしてくれます。 [my_add](auto&&... args) { return my_add(std::forward<decltype(args)>(args...); } のようなイメージです。

関数テンプレートもオーバーロード関数も以下のように書けます。

std::accumulate(v.begin(), v.end(), BOOST_HOF_LIFT(my_add));

tap

tapはpipableで使われることが想定されています。pipableの途中の値を見ることができます。

#include <boost/hof.hpp>
#include <iostream>
namespace hof = boost::hof;

constexpr int add(int a, int b) {
  return a + b;
}

template <typename T>
void print(char const* key, T const& value) {
  std::cout << key << ": " << value << std::endl;
}

int main()
{
  auto added = hof::pipable(add);
  auto result = 3 | added(5) | hof::tap([](auto value) { print("temp", value); }) | added(7);
  print("result", result);
}

出力

temp: 8
result: 15

以上 Boost.HOFの紹介でした。