structured bindings(構造化束縛)を自作クラスで行えるようにする

はじめに

structured bindings (構造化束縛) をstd::pair<>std::tuple<>以外の自作のクラスに対して適用するための方法をコード例込みで解説している日本語の記事が見つからなかったのでメモを兼ねて残しておきます。

結論

  1. std::tuple_size<T>を特殊化する

  2. std::tuple_element<N, T>を特殊化する

  3. get<N>(T)/T::get<N>()を定義する

structured bindings (構造化束縛)

Structured binding declaration (since C++17) - cppreference.com

構造化束縛 - cpprefjp C++日本語リファレンス

structured bindings は C++17 に追加された有用な機能で、関数の返り値からの多値の受け取りなどを簡便に行える言語機能です。

* 関数から複数の値を受け取る例

// 複数の値を返す関数
std::tuple<int, std::string, double> f() {
    return std::make_tuple(42, "foo", 3.14);
}

int main() {
    // 関数からの返り値を受ける
    auto [n, s, d] = f();
    
    assert(n == 42);
    assert(s == "foo");
    assert(d == 3.14);
}

上の例を structured bindings を使わずに書くと例えば次のようになります。

* structured bindings を使わずに関数から複数の値を受け取る例

int main() {
    int n;
    std::string s;
    double d;

    std::tie(n, s, d) = f();
    
    assert(n == 42);
    assert(s == "foo");
    assert(d == 3.14);
}

structured bindings は、std::tuple<>以外にもいくつかの型についてその恩恵を受けることが出来ます。

* std::tuple<>以外の多値型を受け取る例

{
    // std::pair<>
    auto [a, b] = std::make_pair("bar", 100);
}
    
{
    // 生配列
    int raw_array[3] = {1, 2, 3};
    
    auto [a, b, c] = raw_array;
}
    
{
    // std::array<>
    std::array<int, 3> array = {1, 2, 3};
    
    auto [a, b, c] = array;
}

{
    // 構造体
    // 非staticなメンバ変数はすべてpublicである必要がある
    struct S {
        std::string a;
        int b;
    };
    
    auto [a, b] = S { "bar", 10 };
}

自作クラスに structured bindings を適用する

上記に当てはまらないようなクラスについても structured bindings を適用することが出来ます。

例えば次のような privateな 非staticメンバ変数を持つクラスColorを考えます。

* 自作クラス

class Color {
    std::uint32_t c;

public:
    Color(std::uint32_t c) : c(c) {}

    std::uint8_t red() const { return c >> 16; }
    std::uint8_t green() const { return c >> 8; }
    std::uint8_t blue() const { return c >> 0; }
};

(Colorは内部に色の16進表現を保存し、メンバ関数red(), green(), blue() でRGBそれぞれの色要素を取り出せるようなクラスを想定しています)

これをauto [r, g, b] = ...のように受け取りたいのですが、そのままでは当然エラーになってしまいます。

int main() {
    Color orange = 0xff8000;

    auto [r, g, b] = orange; // エラー: cannot decompose non-public member 'Color::c' of 'Color'
}

一度std::tuple<>に変換する関数を用意してauto [r, g, b] = orange.as_tuple();のように書いてもいいのですが直截的でないので別の方法をとります。

1. std::tuple_size<>を特殊化する

まずはじめに、structured bindings で受け取る変数の個数をコンパイラに示します。 コンパイラstd::tuple_size<>::valueの値でこれを確認するため、std::tuple_size<Color>を特殊化します。

* std::tuple_size<>の特殊化

namespace std {
    template <>
    struct tuple_size<Color> : integral_constant<size_t, 3> {}; // Color は 3要素である
}

2. std::tuple_element<>を特殊化する

次に要素の型をコンパイラに示します。 これにはstd::tuple_element<>::typeが使われるため、std::tuple_element<N, Color>を特殊化します。

* std::tuple_element<>の特殊化

namespace std {
    template <size_t N>
    struct tuple_element<N, Color> {
        using type = uint8_t; // 要素の型はすべて std::uint8_t
    };
}

3-a. フリー関数get<>()を定義する

最後に実際に要素の値を取り出します。 これには関数get<N>(Color)が用いられるのでそれを定義します。

使用される関数はADLによって探索されるので目的のクラスと同じ名前空間に定義すれば良いでしょう。

* get<>()の定義

template <std::size_t N>
uint8_t get(const Color& c) {
    if constexpr (N == 0)
        return c.red();
    else if constexpr (N == 1)
        return c.green();
    else
        return c.blue();
}

以上で自作クラスColorに structured bindings を適用することが可能になります。

* Colorに対する structured bindings

int main() {
    Color orange = 0xff8000;
    
    auto [r, g, b] = orange;
    
    assert(r == 255);
    assert(g == 128);
    assert(b ==   0);
}

3-b. メンバ関数get<>()を定義する

フリー関数get<>()の代わりにメンバ関数get<>()を使用することも可能です。

* メンバ関数Color::get<>()の定義

class Color {
    ...

    template <std::size_t N>
    std::uint8_t get() const {
        if constexpr (N == 0)
            return red();
        else if constexpr (N == 1)
            return green();
        else
            return blue();
    }
};

フリー関数get<N>(Color)と、メンバ関数Color::get<N>()が同時に定義されていた場合、メンバ関数Color::get<N>()が優先的に使用されます。

まとめ

structured bindings はGCC/ClangのみでなくMSVCでも利用することの出来る優れた機能です。

C++は複数の値を関数から返すことのできない言語だ」などと揶揄されることもしばしばありましたが今やその限りではありません。

* 全コード

#include <cassert>
#include <tuple>
#include <iostream>

class Color {
    std::uint32_t c;

public:
    Color(std::uint32_t c) : c(c) {}

    std::uint8_t red() const { return c >> 16; }
    std::uint8_t green() const { return c >> 8; }
    std::uint8_t blue() const { return c >> 0; }
    
    template <std::size_t N>
    std::uint8_t get() const {
        std::cout << "Color::get<" << N << ">()" << std::endl;
    
        if constexpr (N == 0)
            return red();
        else if constexpr (N == 1)
            return green();
        else
            return blue();
    }
};

template <std::size_t N>
uint8_t get(const Color& c) {
    std::cout << "get<" << N << ">(Color)" << std::endl;
    
    if constexpr (N == 0)
        return c.red();
    else if constexpr (N == 1)
        return c.green();
    else
        return c.blue();
}

namespace std {
    template <>
    struct tuple_size<Color> : integral_constant<size_t, 3> {}; // Color は 3要素である
    
    template <size_t N>
    struct tuple_element<N, Color> {
        using type = uint8_t; // 要素の型はすべて std::uint8_t
    };
}

int main() {
    Color orange = 0xff8000;
    
    // こう受け取りたい
    auto [r, g, b] = orange;
    
    assert(r == 255);
    assert(g == 128);
    assert(b ==   0);
}

MSVCでempty base optimizationの効かないパターンと対策

問題

2つ以上の空クラスを継承するようなクラスについて、MSVCで empty base optimization (EBO) が期待した通りに働かず、余分な領域が消費される。

struct Empty1 {};
struct Empty2 {};

// 1つの空クラスを基底に持つ
struct Derived1 : Empty1 {
    int i;
};

// 2つの空クラスを基底に持つ
struct Derived2 : Empty1, Empty2 {
    int i;
};

static_assert(sizeof(Empty1) == 1);
static_assert(sizeof(Empty2) == 1);

static_assert(sizeof(Derived1) == sizeof(int));
static_assert(sizeof(Derived2) == sizeof(int)); // MSVCにてエラー

対策

__declspec(empty_bases)拡張属性をEBOを期待するクラスの宣言に付与する。

struct __declspec(empty_bases) Derived2 : Empty1, Empty2 {
    int i;
};

static_assert(sizeof(Derived2) == sizeof(int));

参考: Optimizing the Layout of Empty Base Classes in VS2015 Update 2 | Visual C++ Team Blog

empty base optimization

C++では空クラス(メンバ変数を持たず、仮想メンバ関数を持たないクラス)のサイズは必ず 1以上の大きさを持ちます。 (現行のほとんどの環境で空クラスのサイズは1になります)

struct Empty {};

static_assert(sizeof(Empty) > 0); // 多くの環境で sizeof(Empty) == 1

EBOはその様な空クラスを継承する際のメモリレイアウトに関する最適化の一種で、 継承先のクラスに余分な領域を確保しないようにする効果があります。

struct Empty {}; // 空クラス
struct NotEmpty { char c; }; // 空でないクラス

// どちらもサイズは1byte
static_assert(sizeof(Empty)    == 1);
static_assert(sizeof(NotEmpty) == 1);

// 基底クラスが空クラスなのでEBOが効く
struct A : Empty { int i; };

static_assert(sizeof(A) == sizeof(int));

// 基底クラスが空でないので
// メンバ変数と基底クラスの領域 (+ アライメントのためのパディング)分のサイズを必要とする
struct B : NotEmpty { int i; };

static_assert(sizeof(B) > sizeof(int));

例えばsizeof(int) == 4, alignof(int) == 4であるようなある環境ではsizeof(A) == 4, sizeof(B) == 8となりました。

MSVCでの動作

MSVCではただ1つの空クラスを継承するようなクラスに関して他のコンパイラ同様EBOが働きますが、 2つ以上の空クラスを継承する場合 (デフォルトでは) EBOが働きません

上に挙げた参考サイトの記述によると、

The Visual C++ compiler has historically had limited support for EBCO; however, in Visual Studio 2015 Update 2, we have added a new __declspec(empty_bases) attribute for class types that takes full advantage of this optimization.

VS2015 Update 2 以降では クラスの宣言に__declspec(empty_bases)を付与することで複数の空クラスを継承する場合にもEBOが有効になる様です。

struct Empty1 {};
struct Empty2 {};

// 定義に __declspec(empty_bases) を付ける
struct __declspec(empty_bases) Derived1 : Empty1, Empty2 { int i; };

static_assert(sizeof(Derived1) == sizeof(int)); // EBOが効くようになった

// または前方宣言に __declspec(empty_bases) を付ける
struct __declspec(empty_bases) Derived2;

struct Derived2 : Empty1, Empty2 { int i; };

static_assert(sizeof(Derived2) == sizeof(int)); // EBOが効くようになった

MSVCに於いてこのような場合のEBOが依然としてデフォルトで有効にならないのはABI互換性を保つためのようです。

他のコンパイラでもビルドを出来るようにするにはマクロを使った工夫が必要になるでしょう。

#if defined(_MSC_VER) && _MSC_FULL_VER >= 190023918
#  define EMPTY_BASES __declspec(empty_bases) // VS2015 Update 2 以降
#else
#  define EMPTY_BASES
#endif

struct EMPTY_BASES Derived : Empty1, Empty2 { int i; };

static_assert(sizeof(Derived) == sizeof(int));

見た目がダサい

どのような場合に問題になるか

クラス内部のメモリレイアウトに依存したプログラムを書くような場合、EBOが効かないことが問題となります。

理想的にはクラスのメモリレイアウトに極力依存しないことが理想ではありますが、実際問題としてC/C++では特定のメモリレイアウトを期待するようなプログラムを書く必要に迫られる場面も少なからず存在します。

そのような時、プログラムが予想と違う動作をした場合には実際にメモリ上にどの様にクラスが展開されているのかを注意深く確認しましょう。

最近プリプロセス時処理が楽しい

はじめに

Boost.Preprocessorの実装を調べているうちに色々出来ることが分かって楽しくなってきた。

基本的なことから曲芸的なことまで適当にパターンをメモっておきます。

シンボルの結合

#define PP_CAT(a, b) PP_CAT_I(a, b)
#define PP_CAT_I(a, b) a ## b

#define PP_CAT3(a, b, c) PP_CAT3_I(a, b, c)
#define PP_CAT3_I(a, b, c) a ## b ## c

#define X x
#define Y y
#define Z z

PP_CAT(a, b) // => ab
PP_CAT(X, Y) // => xy

PP_CAT3(a, b, c) // => abc
PP_CAT3(X, Y, Z) // => xyz

論理演算

数値 => 0/1

#define PP_BOOL(x) PP_CAT(PP_BOOL_, x)
#define PP_BOOL_0  0
#define PP_BOOL_1  1
#define PP_BOOL_2  1
#define PP_BOOL_3  1
#define PP_BOOL_4  1
// ...

PP_BOOL(0) // => 0
PP_BOOL(1) // => 1
PP_BOOL(2) // => 1
PP_BOOL(3) // => 1

否定

#define PP_NOT(x) PP_CAT(PP_NOT_, PP_BOOL(x))
#define PP_NOT_0 1
#define PP_NOT_1 0

PP_NOT(0) // => 1
PP_NOT(1) // => 0
PP_NOT(2) // => 0
PP_NOT(3) // => 0

AND/OR

#define PP_OR(x, y) PP_CAT3(PP_OR_, x, y)
#define PP_OR_00 0
#define PP_OR_01 1
#define PP_OR_10 1
#define PP_OR_11 1

PP_OR(0, 0) // => 0
PP_OR(0, 1) // => 1
PP_OR(1, 0) // => 1
PP_OR(1, 1) // => 1

#define PP_AND(x, y) PP_CAT3(PP_AND_, x, y)
#define PP_AND_00 0
#define PP_AND_01 0
#define PP_AND_10 0
#define PP_AND_11 1

PP_AND(0, 0) // => 0
PP_AND(0, 1) // => 0
PP_AND(1, 0) // => 0
PP_AND(1, 1) // => 1

条件分岐

#define PP_IF(b, x, y) PP_CAT(PP_IF_, PP_BOOL(b))(x, y)
#define PP_IF_0(x, y) y
#define PP_IF_1(x, y) x

PP_IF(0, a, b) // => b
PP_IF(1, a, b) // => a

引数

先頭/先頭以外の引数の取り出し

#define PP_ARG_HEAD(_0, ...) _0
#define PP_ARG_TAIL(_0, ...) __VA_ARGS__

PP_ARG_HEAD(a, b, c) // => a
PP_ARG_TAIL(a, b, c) // => b, c

N個目の引数の取り出し

#define PP_ARG_GET(n, ...) PP_CAT(PP_ARG_GET_, n)(__VA_ARGS__)
#define PP_ARG_GET_0(_0, ...) _0
#define PP_ARG_GET_1(_0, _1, ...) _1
#define PP_ARG_GET_2(_0, _1, _2, ...) _2
#define PP_ARG_GET_3(_0, _1, _2, _3, ...) _3
...

PP_ARG_GET(0, a, b, c) // => a
PP_ARG_GET(1, a, b, c) // => b
PP_ARG_GET(2, a, b, c) // => c

引数の個数

#define PP_ARG_SIZE(...) PP_ARG_GET_11(, ##__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)

PP_ARG_SIZE() // => 0
PP_ARG_SIZE(a) // => 1
PP_ARG_SIZE(a, b) // => 2
PP_ARG_SIZE(a, b, c) // => 3

この例では10個までの引数を数えられます。
##__VA_ARGS__gcc, clangの拡張です。C++20には __VA_OPT__ が入るので拡張に頼る必要がなくなります。

リスト操作

展開

#define PP_LIST_UNPACK(list) PP_LIST_UNPACK_I list
#define PP_LIST_UNPACK_I(...) __VA_ARGS__

PP_LIST_UNPACK((a, b, c)) // => a, b, c

先頭の要素、先頭を取り除いたリストの取り出し

#define PP_LIST_HEAD(list) PP_ARG_HEAD list
#define PP_LIST_TAIL(list) (PP_ARG_TAIL list)

PP_LIST_HEAD((a, b, c)) // => a
PP_LIST_TAIL((a, b, c)) // => (b, c)

N番目の要素の取り出し

#define PP_LIST_GET(list, n) PP_CAT(PP_ARG_GET_, n) list

PP_LIST_GET((a, b, c), 0) // => a
PP_LIST_GET((a, b, c), 1) // => b
PP_LIST_GET((a, b, c), 2) // => c

要素の個数

#define PP_LIST_SIZE(list) PP_ARG_SIZE list

PP_LIST_SIZE(()) // => 0
PP_LIST_SIZE((a)) // => 1
PP_LIST_SIZE((a, b)) // => 2
PP_LIST_SIZE((a, b, c)) // => 3

リストが空かどうか

#define PP_LIST_IS_EMPTY(list) PP_NOT(PP_LIST_SIZE(list))

PP_LIST_IS_EMPTY(()) // => 1
PP_LIST_IS_EMPTY((a)) // => 0
PP_LIST_IS_EMPTY((a, b)) // => 0
PP_LIST_IS_EMPTY((a, b, c)) // => 0

要素の追加

#define PP_LIST_PUSH_FRONT(list, item) PP_IF(PP_LIST_SIZE(list), (item, PP_LIST_UNPACK(list)), (item))
#define PP_LIST_PUSH_BACK(list, item) PP_IF(PP_LIST_SIZE(list), (PP_LIST_UNPACK(list), item), (item))

PP_LIST_PUSH_FRONT((), a) // => (a)
PP_LIST_PUSH_FRONT((b), a) // => (a, b)
PP_LIST_PUSH_FRONT((b, c), a) // => (a, b, c)

PP_LIST_PUSH_BACK((), a) // => (a)
PP_LIST_PUSH_BACK((a), c) // => (a, b)
PP_LIST_PUSH_BACK((a, b), c) // => (a, b, c)

繰り返し

#define PP_WHILE(pred, op, state) PP_WHILE_0(pred, op, state)
#define PP_WHILE_0(pred, op, state) PP_IF(pred(state), PP_WHILE_1(pred, op, op(state)), state)
#define PP_WHILE_1(pred, op, state) PP_IF(pred(state), PP_WHILE_2(pred, op, op(state)), state)
#define PP_WHILE_2(pred, op, state) PP_IF(pred(state), PP_WHILE_3(pred, op, op(state)), state)
...
#define PP_WHILE_10(pred, op, state) PP_IF(pred(state), PP_WHILE_11(pred, op, op(state)), state)
#define PP_WHILE_11(pred, op, state) PP_WHILE_ERROR

PP_WHILE(PP_LIST_SIZE, PP_LIST_TAIL, (a, b, c)) // => ()
PP_WHILE(PP_LIST_IS_EMPTY, PP_LIST_TAIL, (a, b, c)) // => (a, b, c)

pred(state) が1の間 state <- op(state) を適用し続けます。

擬似コードで書くとこんな感じ

function PP_WHILE(pred, op, state) {
	while (pred(state))
		state = op(state)

	return state
}

ただしマクロ関数は再帰呼び出しが出来ないため同じマクロ関数を複数用意して順番に呼び出していくことで擬似的にループを実現します。
この例では10回までのイテレーションが可能です。

また、PP_WHILE()の引数predまたはopPP_WHILE()を内部で使うマクロ関数は渡せません。
その場合は再帰呼び出しになるためマクロ展開が失敗します。
そのため、PP_WHILE()と同様のマクロ関数を複数用意しておくといいでしょう。

#define PP_WHILE_A(pred, op, state) PP_WHILE_A_0(pred, op, state)
#define PP_WHILE_A_0(pred, op, state) PP_IF(pred(state), PP_WHILE_A_1(pred, op, op(state)), state)
#define PP_WHILE_A_1(pred, op, state) PP_IF(pred(state), PP_WHILE_A_2(pred, op, op(state)), state)
#define PP_WHILE_A_2(pred, op, state) PP_IF(pred(state), PP_WHILE_A_3(pred, op, op(state)), state)
...
#define PP_WHILE_A_10(pred, op, state) PP_IF(pred(state), PP_WHILE_A_11(pred, op, op(state)), state)
#define PP_WHILE_A_11(pred, op, state) PP_WHILE_ERROR

#define PP_WHILE_B(pred, op, state) PP_WHILE_B_0(pred, op, state)
#define PP_WHILE_B_0(pred, op, state) PP_IF(pred(state), PP_WHILE_B_1(pred, op, op(state)), state)
#define PP_WHILE_B_1(pred, op, state) PP_IF(pred(state), PP_WHILE_B_2(pred, op, op(state)), state)
#define PP_WHILE_B_2(pred, op, state) PP_IF(pred(state), PP_WHILE_B_3(pred, op, op(state)), state)
...
#define PP_WHILE_B_10(pred, op, state) PP_IF(pred(state), PP_WHILE_B_11(pred, op, op(state)), state)
#define PP_WHILE_B_11(pred, op, state) PP_WHILE_ERROR

#define PP_WHILE_C(pred, op, state) PP_WHILE_C_0(pred, op, state)
#define PP_WHILE_C_0(pred, op, state) PP_IF(pred(state), PP_WHILE_C_1(pred, op, op(state)), state)
#define PP_WHILE_C_1(pred, op, state) PP_IF(pred(state), PP_WHILE_C_2(pred, op, op(state)), state)
#define PP_WHILE_C_2(pred, op, state) PP_IF(pred(state), PP_WHILE_C_3(pred, op, op(state)), state)
...
#define PP_WHILE_C_10(pred, op, state) PP_IF(pred(state), PP_WHILE_C_11(pred, op, op(state)), state)
#define PP_WHILE_C_11(pred, op, state) PP_WHILE_ERROR
[追記]

BOOST_PP_AUTO_REC()(AUTO RECursion)のような仕組みを用いることで使用すべきPP_WHILE_*を自動的に決定できるためPP_WHILE()の引数にPP_WHILE()を使う関数を渡せるようにできます。

www.slideshare.net

#define PP_AUTO_REC(pred) PP_AUTO_REC_NODE_2(pred)

#define PP_AUTO_REC_NODE_2(p) PP_IF(p(2), PP_AUTO_REC_NODE_1(p), PP_AUTO_REC_NODE_3(p))
#	define PP_AUTO_REC_NODE_1(p) PP_IF(p(1), 1, 2)
#	define PP_AUTO_REC_NODE_3(p) PP_IF(p(3), 3, 4)

#define PP_WHILE PP_CAT(PP_WHILE, PP_AUTO_REC(PP_WHILE_CHECK))
#define PP_WHILE_CHECK(n) PP_CAT(PP_WHILE_CHECK_, PP_WHILE ## n(PP_WHILE_CHECK_P, PP_WHILE_CHECK_O, OK))
#define PP_WHILE_CHECK_P(s) 0
#define PP_WHILE_CHECK_O(s)
#define PP_WHILE_CHECK_OK 1

#define PP_WHILE1 PP_WHILE1_0
#define PP_WHILE_CHECK_PP_WHILE1_0(p, o, s) 0
#define PP_WHILE1_0(pred, op, state) PP_IF(pred(state), PP_WHILE1_1(pred, op, op(state)), state)
#define PP_WHILE1_1(pred, op, state) PP_IF(pred(state), PP_WHILE1_2(pred, op, op(state)), state)
...

#define PP_WHILE2 PP_WHILE2_0
#define PP_WHILE_CHECK_PP_WHILE2_0(p, o, s) 0
#define PP_WHILE2_0(pred, op, state) PP_IF(pred(state), PP_WHILE2_1(pred, op, op(state)), state)
#define PP_WHILE2_1(pred, op, state) PP_IF(pred(state), PP_WHILE2_2(pred, op, op(state)), state)
...

#define PP_WHILE3 PP_WHILE3_0
#define PP_WHILE_CHECK_PP_WHILE3_0(p, o, s) 0
#define PP_WHILE3_0(pred, op, state) PP_IF(pred(state), PP_WHILE3_1(pred, op, op(state)), state)
#define PP_WHILE3_1(pred, op, state) PP_IF(pred(state), PP_WHILE3_2(pred, op, op(state)), state)
...

#define PP_WHILE4 PP_WHILE4_0
#define PP_WHILE_CHECK_PP_WHILE4_0(p, o, s) 0
#define PP_WHILE4_0(pred, op, state) PP_IF(pred(state), PP_WHILE4_1(pred, op, op(state)), state)
#define PP_WHILE4_1(pred, op, state) PP_IF(pred(state), PP_WHILE4_2(pred, op, op(state)), state)
...

算術演算

インクリメント/デクリメント

#define PP_INC(x) PP_CAT(PP_INC_, x)
#define PP_INC_0  1
#define PP_INC_1  2
#define PP_INC_2  3
...
#define PP_INC_9  10
#define PP_INC_10 10

#define PP_DEC(x) PP_CAT(PP_DEC_, x)
#define PP_DEC_0  0
#define PP_DEC_1  0
#define PP_DEC_2  1
...
#define PP_DEC_9  8
#define PP_DEC_10 9

PP_INC(0) // => 1
PP_INC(1) // => 2
PP_INC(2) // => 3
PP_INC(10) // => 10

PP_DEC(0) // => 0
PP_DEC(1) // => 0
PP_DEC(2) // => 1
PP_DEC(10) // => 9

結果の値が[0, N]の範囲にクランプされてたほうが扱いやすいのでPP_INC(10) == 10, PP_DEC(0) == 0になるようにしています。

加算

PP_WHILE()PP_INC(), PP_DEC()を用いて繰り返し数値をインクリメントすることで実現します。

#define PP_ADD(x, y) PP_LIST_GET(PP_WHILE(PP_LIST_HEAD, PP_ADD_O, (x, y)), 1)
#define PP_ADD_O(xy) (PP_DEC(PP_LIST_GET(xy, 0)), PP_INC(PP_LIST_GET(xy, 1)))

PP_ADD(2, 3) // => 5
PP_ADD(5, 2) // => 7

結果は[0, 10]の範囲に丸められます。

PP_WHILE()を内部で使用するため、別のPP_WHILE_*を使う同様の関数を用意しておくといいです。

#define PP_ADD_A(x, y) PP_LIST_GET(PP_WHILE_A(PP_LIST_HEAD, PP_ADD_O, (x, y)), 1)
#define PP_ADD_B(x, y) PP_LIST_GET(PP_WHILE_B(PP_LIST_HEAD, PP_ADD_O, (x, y)), 1)
#define PP_ADD_C(x, y) PP_LIST_GET(PP_WHILE_C(PP_LIST_HEAD, PP_ADD_O, (x, y)), 1)

減算

#define PP_SUB(x, y) PP_LIST_GET(PP_WHILE(PP_LIST_HEAD, PP_SUB_O, (y, x)), 1)
#define PP_SUB_O(xy) (PP_DEC(PP_LIST_GET(xy, 0)), PP_DEC(PP_LIST_GET(xy, 1)))

PP_SUB(2, 3) // => 0
PP_SUB(5, 2) // => 3

#define PP_SUB_A(x, y) PP_LIST_GET(PP_WHILE_A(PP_LIST_HEAD, PP_SUB_O, (y, x)), 1)
#define PP_SUB_B(x, y) PP_LIST_GET(PP_WHILE_B(PP_LIST_HEAD, PP_SUB_O, (y, x)), 1)
#define PP_SUB_C(x, y) PP_LIST_GET(PP_WHILE_C(PP_LIST_HEAD, PP_SUB_O, (y, x)), 1)

結果は[0, 10]の範囲に丸められます。

同様に繰り返し加減算を行うことで乗算や除算、剰余なども実装できますが割愛します。

さて、これで条件分岐、繰り返しと加減算が扱えるようになったので色々なものが書けます。

(0, 1, 2, ..., n-1)のようなリストを生成したり、

#define PP_IOTA(n) PP_LIST_GET(PP_WHILE(PP_LIST_HEAD, PP_IOTA_O, (n, ())), 1)
#define PP_IOTA_O(nl) (PP_DEC(PP_LIST_GET(nl, 0)), PP_LIST_PUSH_FRONT(PP_LIST_GET(nl, 1), PP_DEC(PP_LIST_GET(nl, 0))))

PP_IOTA(0) // => ()
PP_IOTA(3) // => (0, 1, 2)
PP_IOTA(10) // => (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

リストを逆順にしたり、

#define PP_REVERSE(list) PP_LIST_GET(PP_WHILE(PP_REVERSE_P, PP_REVERSE_O, (list, ())), 1)
#define PP_REVERSE_P(io) PP_LIST_SIZE(PP_LIST_GET(io, 0))
#define PP_REVERSE_O(io) (PP_LIST_TAIL(PP_LIST_GET(io, 0)), PP_LIST_PUSH_FRONT(PP_LIST_GET(io, 1), PP_LIST_HEAD(PP_LIST_GET(io, 0))))

PP_REVERSE(PP_IOTA(5)) // => (4, 3, 2, 1, 0)

リストの各要素に関数を適用したり。

#define PP_MAP(list, f) PP_LIST_GET(PP_WHILE(PP_MAP_P, PP_MAP_O, (list, f, ())), 2)
#define PP_MAP_P(ifo) PP_LIST_SIZE(PP_LIST_GET(ifo, 0))
#define PP_MAP_O(ifo) PP_MAP_O_I ifo
#define PP_MAP_O_I(in, f, out) (PP_LIST_TAIL(in), f, PP_LIST_PUSH_BACK(out, f(PP_LIST_HEAD(in))))

#define DOUBLE(x) PP_ADD_A(x, x)
PP_MAP(PP_IOTA(4), DOUBLE) // => (0, 2, 4, 6, 8)

#define ADD_PREFIX_n(x) PP_CAT(n, x)
int PP_LIST_UNPACK(PP_MAP(PP_IOTA(3), ADD_PREFIX_n)); // => int n0, n1, n2;

最後の例とかC++03書く時とか便利だったりするんじゃないでしょうか。

まとめ

以上全コード
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ


プリプロセッサでも様々なことが出来るのがわかったと思います。
手触りとしては再帰呼び出しと算術演算の出来ないSchemeって感じです。
試してみると結構楽しいのでおすすめです(?)

コードはまだ洗練できていませんがFizz Buzzとクイックソートを実装したので供養しておきます。

月曜日の空飛ぶオレンジ

この記事は まんがタイムきらら Advent Calendar 2017 - Adventar の17日目の記事です。

www.adventar.org

はじめに

今年も半月を残し、師走の慌ただしさを感じる頃合いになりました。

きららファンタジアのサービスも開始し、さらに来る年の頭には待ちに待った ゆるキャン△ と、なにより スロウスタート のアニメが放送されます。

たまてちゃんのツインテールがふよんふよん動く様が楽しみです。

さて、完結済みの作品には素晴らしい作品が多く存在しますがそれらに関する情報を目にする機会は、当然掲載中の作品に関するそれと比べ少ないです。

というわけで、折角の機会なので自分の好きな完結済み作品の話をしたいと思います。

月曜日の空飛ぶオレンジ

月曜日の空飛ぶオレンジ。 1巻 (まんがタイムKRコミックス)

月曜日の空飛ぶオレンジ。 2巻 (まんがタイムKRコミックス)

情報

著者: あfろ

掲載誌: まんがタイムミラク (vol.1 ~ 平成25年7月号)

巻数: 全2巻

紹介

スイカの自販機。

朝起きたら頭が箱に。

植木鉢に信号機。

キュート&フリーダムなこの興奮、初体験。

Amazon - 月曜日の空飛ぶオレンジ (1) 作品内容より

月曜日の空飛ぶオレンジ は現在 ゆるキャン△ (きららフォワード) と mono (ミラクからキャラットへ移籍) を連載されている あfろ先生 の前々作です。

この作品はいわゆる不条理系です。

時速100kmのスイカ、上から降ってくる金ダライ、タイムトラベルするカレーライス。

架空の離島、六日島を舞台に女子高生と 犬と 謎のオレンジ色の生物つかぽんによる不思議な日常を描いたマンガです。

正直なところ初めてこれを読んだときには、異様な世界観になんだこれと半ば首を捻りながら読んでいましたが、 この雰囲気に慣れる頃にはテンポの良い畳み掛けるようなギャグが段々と癖になっていきます。

かわいらしい絵柄とパースを効かせた背景、犬、バイク……。あfろ先生らしさに溢れた癖の強い作品です。

数式微分器の作成 (2) 式木の最適化

前回の記事の続きです。

数式微分器の作成 と D言語での木走査 - 茅の下

前回の内容

前回の記事で数式微分器を作成したが、出力が繁雑になった。

結論

今回はそれを解消するために式木に対してより強力な最適化を掛けるようにした結果、出力がこましになった。

前回の反省

前回、出力が繁雑になった原因は主に次の2つです。

  1. 割り算ノードを用意しなかった。

    前回は必要な式木の型数を小さくするために、割り算に相当するノード型を用意しませんでした。

    そのため、{ f / g } f \times g^{-1} のように表現する必要があり、これが出力の式木のノード数が増える原因になっていました。

    今回は割り算ノードを用意しました。

  2. 式木に対する最適化が甘かった。

    前回式木に対して施していた最適化は以下の3項目のみでした。

    1. 加算両辺の 0 の削除。

       0 + b → b

       a + 0 → a

    2. 乗算両辺の 1 の削除。

       1 \times b → b

       a \times 1 → a

    3. 0 を含む乗算の削除。

       0 \times b → 0

       a \times 0 → 0

    今回はこれらに加えて式木に対して様々な最適化を行いました。

GitHub - Ryooooooga/Calculator

結果

では前回と同様、 1 / tan(x) (= cos(x) \times sin(x)^{-1})微分してみます。

以下が前回の出力結果です。

((((-1)*sin(x))*sin(x)^{(-1)})+(cos(x)*(sin(x)^{(-1)}*((cos(x)*(-1))*sin(x)^{(-1)}))))

以下が今回の最適化を施したあとの出力です。

(-1-((cos(x)^2)/(sin(x)^2)))

依然として括弧だらけですが、全体的にこざっぱりしました。

なお、二階微分すると爆発します。

-(-(((((((cos(x)^2)*(2*sin(x)))*(sin(x)^2))/cos(x))+(((cos(x)^2)*((sin(x)^2)*(2*cos(x))))/sin(x)))/((sin(x)^2)^2))))

最近思ったこと; 単行本に挟んであるあれ

単行本に挟んであるあれ

ミックスとかラノベの新刊に挟んであるレーベルの広告的な8つ折りくらいの小冊子、ページに色が移るインクが使われていると、つらい。

はいふり 人称行列

はいふりのキャラクター間の一、二、三人称を纏めています。

docs.google.com

現在本編とOVA前編・後編、加えてローレライの乙女たち3話まで完了しています。

収集の条件は大まかに、

  • 呼び掛ける側が呼ばれる側の名前を認識していること。

    (例えば、2話気絶しているミーナに対して岬が呼び掛けた「『あなた』、生きてるよ」などはこの時点で岬がミーナの名前を認識していないため含みません)

  • 呼び掛ける側が呼ばれる側の個人を特定していること。

    (『あなたたち』の様に呼ばれる側が複数形の特定されないものなどは含みません)

の二つです。ただし、直前の会話文との重複を避けるために用いられる『あなた』などは例外的に含まないものとします。

また、特別な状況下での呼び掛け(幼少期の ましろ から真霜に対する『お姉ちゃん』など)の場合は可能な限りその話数を併記します。

何かの役に立てばいいです。