進捗置き場というわけでもない場所

プログラミングしてる中で見つけたこととか

JOI 2017-2018 本選参加記

自戒の意をこめて問題に関するものだけ書きます。

問題は https://www.ioi-jp.org/joi/2017/2018-ho/index.html で見てください。

1 問目

やるだけ。

適当に不連続な部分の長さをカウントして、短い方から使う。

#include <iostream>
#include <vector>
#include <cstdint>
#include <algorithm>

int main() {
    int n, k;
    std::cin >> n >> k;

    std::vector<int64_t> times(n);
    for (auto&& e : times)
        std::cin >> e;
        
    std::sort(times.begin(), times.end());

    int block = n;
    std::vector<int64_t> blanks;
    blanks.reserve(n);

    for (int i = 1; i < n; ++i) {
        if (times[i - 1] != times[i] - 1)
            blanks.push_back(times[i] - times[i - 1] - 1);
        else
            --block;
    }

    std::sort(blanks.begin(), blanks.end());
    block -= k;

    int64_t ans = n;
    for (int i = 0; i < block; ++i)
        ans += blanks[i];
    
    std::cout << ans << std::endl;
}

2 問目

大きさに関しては最大最小のみが重要なのでとりあえず大きさでソートしてみる。

するとソートした中では連続している美術品を展示する方が良いのがわかる。

また、隣合う美術品の大きさの差を diff とすると例えば入力例 1 では次のようになる。

f:id:azaika:20180212232621p:plain

水色の枠の中にあるのは美術品の価値。

今回は大きさでソートすると [(2, 3), (4, 5), (11, 2)] みたいになるので図のような感じになる。

すると求めたい最大値は「ある美術品と美術品の間の価値の合計からその間にある diff を全て引いた数の最大値」と言い換えられる。

例えば入力例 1 では最初の美術品の価値が 3、次の美術品との diff が 2、二つ目の美術品の価値が 5 で、これを考えた 3 + 5 - 2 = 6 が最良になる。

そう考えるとこの問題は「数列(もどき)の区間和を最大化する」問題に帰着できる。

一瞬セグ木を書きそうになるが、グッと堪えると別に配列を舐めながら走査すれば求まることに気づく。

そんな感じで思考して AC。

#include <iostream>
#include <cstdint>
#include <vector>
#include <algorithm>

int main() {
    int64_t n;
    std::cin >> n;

    std::vector<std::pair<int64_t, int64_t>> ab(n);
    for (auto&& e : ab)
        std::cin >> e.first >> e.second;

    std::sort(ab.begin(), ab.end());

    int64_t ans = 0;
    int64_t sum = 0, min = 0;
    for (int i = 0; i < n; ++i) {
        sum += ab[i].second;

        ans = std::max(ans, sum - std::min(min, sum));
    
        if (i < n - 1)
            sum -= ab[i + 1].first - ab[i].first;

        min = std::min(min, sum);
    }
    
    std::cout << ans << std::endl;
}

3 問目

最初団子が刺さる向きが規定されていないと思ってしまいグラフ問題か?となるが、問題をちゃんと読むと刺さる向きは左→右か上→下と書いてある。

考えると左肩下がりの斜め向きの RGW だけ考えればそれ以外は干渉しない気がするので、斜め向きに DP して後で全部足す。提出。小課題 1 以外通らない。

コンテストが残り 1 時間のため「きっと解法が違うんだろう」と諦めて他の問題の部分点を狙おうとする。

これが結果的に大間違いだった事が後で分かる。解法は完全に正しくて実装がバグっていただけだったっぽい。

4 問目

満点解法は微塵も思いつかず、小課題 2 と 3 は分かったため書こうとする。Dijkstra の経路復元をバグらせて Ubuntu仮想マシンがフリーズする。焦る。

バグを直したところでタイムアップになり、提出できずに終わる。

5 問目

問題だけ読んでデータ構造っぽいとか言ってた。大嘘だった。

総評

2 完 + 3 問目の部分点で 213 点。おそらく 30 位前後だと思う。春合宿には行けなかった。

敗因が明確すぎてとても悔しかった。去年部分点を取らなかったせいで春合宿を逃したのが、今年は部分点を取ろうとしたせいで春合宿を逃すという良い失敗の事例っぽい感じ。

相方がやる気を出し、僕もこれで終わりたくないので PCK には多分出ます。

1 ページで眺める C++17

この記事は C++AdC 2017 19 日目の記事です。

n 番煎じ過ぎてみなさんがやらなかったやつをやります。

色々と cpprefjp江添さんのやつ を参考にしています。記述が被っている場所すらあるかもしれません。ごめんなさい。

言語機能

コードでの説明が必要なやつから紹介。だんだん雑になるかも。

構造化束縛(structured bindings)

今まで std::tuple を扱うのは面倒で、例えば

std::tuple<int, double, std::string> tup = f(); // auto tup = f(); でも良い
auto x = std::get<0>(tup);
auto d = std::get<1>(tup);
auto s = std::get<2>(tup);

または

int x;
double d;
std::string s;
std::tie(x, d, s) = f();

みたいに書く必要があった。これは微妙に使う気が起きない。C++17 からはこう書ける

auto [x, d, s] = f();

これなら使う気がぐんぐん湧いてくる。

これは tuple に限った機能ではなく、配列、条件を満たすクラスなどにも使える

auto [a1, a2, a3] = { 5, 1, 3 };

struct Pos {
    int x = 10, y = 10, z = 10;
};
auto [x, y, z] = Pos();

便利だね。

ただ

int a = 5, b = 3;
[a, b] = std::minmax(a, b);

みたいなことは出来ない。あくまで構造化束縛は変数の定義/初期化のみの構文に留まっている。

こういう便利そうな機能が微妙な仕様で終わってるあたり C++ らしくていとあはれなり(社会性フィルター)。

条件式書くときの構文変更

書くたびになんか微妙な気がするこういうコード。

auto result = f();
if (result.isOK()) {
    // 処理
}

C++17 からはこう書ける

if (auto result = f(); result.isOK()) {
    // 処理
}

この構文は if だけでなく、whileswitch などでも使える。

template 引数推論

初期化時にちょっと嫌になるこういうやつ

std::vector<std::vector<int>> vec(m, std::vector<int>(n, x));

C++17 からはこう書ける

std::vector vec(m, std::vector<int>(n, x));

これは std::vector の template 引数が推論されるようになったから。

ある程度は自動で推論してくれるし、もし自動では出来ないような推論をさせたい場合

template <typename T>
struct Vec {
    std::vector<T> vec; // コンテナが渡されたとき、それを std::vector に持ち替えたい
    
    template <typename C>
    Vec(const C& rhv) : vec(rhv.begin(), rhv.end()) {}
};

template <typename C>
Vec(const C&) -> Vec<typename C::value_type>; // コンテナの value_type を T とする

のように Hoge(引数) -> Hoge<推論する型> という構文で推論させることができる。

これを利用して明示的に文字数を指定しないでコンパイル時文字列を扱えるようになったりするのを 組んでみた けど、割りと面白かった。

RVO の保証

今までこんなコードを書いた時、返り値のオブジェクトがコピーされる可能性があった。

std::vector<int> f() {
    auto c = hoge(); // 何かしらのコンテナが帰ってくる

    return std::vector<int>(c.begin(), c.end()); // ここで返り値のコピーが発生する可能性がある
}

なぜ「可能性」なのかというと、これをコピーしないようにする最適化、Return Value Optimization (RVO) が規格上認められていたからなのだが、C++17 からはこの関数 f の返り値を利用して値を初期化する場合。すなわち

auto v = f();

のようなユースケースの場合はこの最適化が強制されるようになった。

これにより、上記のコードで必ずコピーが発生しないようになる。

訂正: 記事公開時点では

std::vector<int> f() {
    std::vector<int> vec = hoge();
    
    // なんか処理

    return vec;
}

のようなコードで RVO が保証されるかのような書き方をしていましたがこれは誤りでした。関数の返り値が prvalue カテゴリでかつ変数の初期化に使われるときのみ RVO が保証されるようです。

ご指摘 していただいた銀天すばるさんに感謝。

template

template で任意の型のコンパイル時定数を渡したい人に朗報

template <auto X>
struct Hoge {
    // 色々
};

のようなコードを書くことで

Hoge<30> a;
Hoge<42ll> b;

void f() { std::cout << "Hello, world!" << std::endl; }
Hoge<f> c;

みたいなコードを書くことができる。局所的に役立ちそう。

constexpr if

あの C++ TMP を象徴する機能、SFINAE の地位を半分奪うかもしれない素晴らしい機能。

template <
    typename T, 
    std::enable_if_t<
        std::is_same_v<std::decay_t<T>, int>,
        std::nullptr_t
    > = nullptr
>
void f(T) {
    std::cout << "T is int" << std::endl;
}

template <
    typename T,
    std::enable_if_t<
        !std::is_same_v<std::decay_t<T>, int>
            && std::is_integral_v<T>,
        std::nullptr_t
    > = nullptr
>
void f(T) {
    std::cout << "T is integer but not int" << std::endl;
}

template <
    typename T,
    std::enable_if_t<
        !std::is_integral_v<T>,
        std::nullptr_t
    > = nullptr
>
void f(T) {
    std::cout << "T is not integer" << std::endl;
}

みたいなよくある地獄コード(実際はもうちょっとスマートに書けるかも)が、なんとこうなる。

template <typename T>
void f(T) {
    if constexpr (std::is_same_v<std::decay_t<T>, int>)
        std::cout << "T is int" << std::endl;
    else if constexpr (std::is_integral_v<T>)
        std::cout << "T is integer but not int" << std::endl;
    else
        std::cout << "T is not integer" << std::endl;
}

なんということでしょう。もうあのクソオーバーロード解決をしなくて良いのです。めでたしめでたし。

ところが一つ罠があり、この constexpr if の操作は Two Phase Lookup の二段階目で行われるため、パースした段階で明らかにエラーである

template <typename T>
void f() {
    if constexpr (false)
        static_assert(false);
    else
        std::cout << "fine" << std::endl;
}

とかのコンパイルが通らない。この解決策としてはコードを

template <typename T>
void f() {
    if constexpr (false)
        static_assert(std::is_integral_v<T> && false);
    else
        std::cout << "fine" << std::endl;
}

こうすると static_assert の中身が一応 T に依存するので、コンパイルが通るようになる。前からある問題とは言えとても C++ らしいエラーの出し方でいとあはれなり。

畳み込み演算(Fold Expression)

今までこう書いてたやつが

template <typename T>
auto sum(T x) {
    return x;
}

template <typename T, typename... Types>
auto sum(T x, Types... args) {
    return x + sum(args...);
}

こう書けるようになる

template <typename... Types>
auto sum(Types... args) {
    return (... + args);
}

これは (((args#0 + args#1) + args#2) + ...) みたいに展開される(表記は江添さんが使ってるやつです)。

(args#0 + (args#1 + (args#2 + ...))) みたいに展開したい場合

template <typename... Types>
auto sum(Types... args) {
    return (args + ...);
}

とすればいい。

注意点として、式全体を () でくくらないとエラーになる。

template <typename... Types>
auto sum(Types... args) {
    return ... + args; // 括弧がないのでエラー!
}

あと、畳み込む演算自体は結構自由にできる。こんなんでもいい。

template <typename... Types>
auto sum(Types... args) {
    return (... + f(args).hoge());
}

例では + 演算子を使っているが演算子ならだいたい使える。

余談: これを悪用してみたいなぁとか考える人向けの某中3女子さんのツイートがこちら

constexpr ラムダ

  • コードの説明ないけど重要そうだから上の方入れた
  • とうとう constexpr なラムダ式が作れるようになった
  • 特にキーワードも必要なく、単にコンパイル時に実行できそうなラムダはコンパイル時の文脈で使える
  • つまり constexpr な変数とか static_assert の条件式内でラムダ式が使える
  • 色々できそうだが、なんとSFINAEでの悪用を防ぐためにtemplate引数の中には書けないので注意

ラムダ式の *this キャプチャ

以下のコードには重大なバグがある

struct Hoge {
    int x;

    auto f() {
        return [this]() { return this->x++; };
    }
}

int main() {
    std::function<int()> f;
    {
        Hoge hoge{5};
        f = hoge.f();
        std::cout << f() << std::endl;
    }

    std::cout << f() << std::endl;
}

そう、最後のラムダ式内で this をキャプチャしたはいいものの、最後の f() の呼び出し時には hoge の寿命は切れている。つまりキャプチャしたはずの this の中身が開放されているので見事未定義動作を踏み抜くことになる。

これを回避するため C++17 では this の中身である *this を直接キャプチャできるようになった。

struct Hoge {
    int x;

    auto f() {
        return [*this]() { return this->x++; }; // *this がコピーキャプチャされるので寿命も安心
    }
}

int main() {
    std::function<int()> f;
    {
        Hoge hoge{5};
        f = hoge.f();
        std::cout << f() << std::endl;
    }

    std::cout << f() << std::endl;
}

ネストされた名前空間定義の省略

いままで書いていた

namespace A {
    namespace B {
        namespace C {
            // ここに色々書く
        }
    }
}

みたいな非常に微妙にアレなやつが

namespace A::B::C {
    // ここに色々書く
}

と書けるようになった。嬉しい。

属性の追加

[[fallthrough]][[nodiscard]][[maybe_unused]] が追加された。

[[fallthrough]] は switch 文の中で用いて

switch (hoge) {
    case A:
        // なんか処理
        [[fallthrough]]
    case B:
        // なんか処理
        break;
    default:
        break;
}

みたいにすると case A の最後の部分で警告が出なくなる。

[[nodiscard]]は関数の定義につけると、その関数の返り値が使われていないときに警告を出す。標準ライブラリでも malloc などいくつかの低レベルのメモリ操作関数にこの指定がついた。

[[maybe_unused]] は変数の定義につけるとその変数がその先使われていなくても警告を出さない。デバッグとかに便利かも。

一部演算子の評価順序の固定

int& f(int& x, int v) {
    std::cout << v;
    return x;
}

int x = 0;
f(x, 2) = f(x, 1);

このようなコードを書いたとき、今までは演算子の右辺左辺の評価順序が未規定だったのでこの結果は 12 にも 21 にもなり得た。

C++17 からは例えば b = a のとき a => b の順に評価されるされるのが保証された。結果さっきのコードの実行結果は必ず 12 になる。

このようにして評価順序が固定される演算一覧はこんな感じ。全部 a => b の順に評価される。

a.b
a->b
a->*b
b = a
b @= a // @ は任意の演算子
a[b]
a << b
a >> b

なお期待されていた f(a, b, c) などの関数呼び出しにおける引数 a, b, c の評価順序の固定はされなかった。未だに未規定です。

その他

コードで説明する感じでもない色々を箇条書きしていく。

noexcept 指定が関数の型の一部に

  • 今まで void f(int x)void g(int x) noexcept は同じ void(int) 型だった
  • C++17 からはこれが区別され、上の例でいえば g の型は void(int) noexcept になる
  • void(int) noexcept から void(int) には暗黙の変換がされるが、逆はされない
  • template 周りで既存のコードが動かなくなる可能性があるので注意
  • こんなところに入れてるけど結構重要な変更

byte 型の追加

  • 他の整数からの暗黙変換が不可能な 8bit 整数型、byte 型が追加された
  • ストレージ用の型なのか、四則演算が定義されていない

inline 変数

  • inline Hoge x; とかで inline な変数が作れるようになった
  • ヘッダーオンリーライブラリの幅が広がるね

static_assert の記法追加

  • static_assert(cond, "")static_assert(cond) と書けるようになった

using の可変長引数の展開が可能に

  • using Args::operator()...; みたいな事ができます
  • というか今までできなかったのに気づいたときは驚いた

UTF-8 文字リテラル

  • u8'A' で UTF-8 での A を表現できる
  • ただし、1 バイトの文字しか扱えないため u8'あ' などはエラー
  • なぜ char8_t が入らないのにこんなものを入れたのか

16 進数の浮動小数点リテラル

  • 0x0.01p0 => 1/256 = 0.00390625 みたいな感じ。p 以下は 10 進数で指数部を指定する

属性名前空間を using できるように

  • [[using ns : foo, bar]] int x; みたいにすると [[ns::foo, ns::bar]] int x; と同じ動作になる

トライグラフの削除

ライブラリ

全体的に触れるだけにします。ファイルシステムに関しては説明を放棄しています。

あと並び順はあまり重要度と関係がないため、実は重要な事が下に小さく書かれてたりするかも。

std::string_view

今まである文字列の部分文字列を取得したいときには substr を使って次のようにしていた。

std::string str = "kitty on your lap.";

std::cout << str.substr(2, 3) << std::endl; // => tty

これは便利ではあるが、一方で substr の返り値が std::string であるために、その生成を生成するための文字列コピーが走るというパフォーマンス的な問題が生じていた。

要はただ文字列の一部を参照するためだけにそのコピーを作るのは無駄という話。

というわけで文字列の参照のみを持つ std::string_view というのが入った。<string_view> を include することで使用可能になる。

これを使うとさっきのコードも

#include <string_view>

//...

std::cout << std::string_view(str).substr(2, 3) << std::endl;

こうすれば無駄なコピーが走ることはない。めでたしめでたし。

ただし、string_view が参照している文字列を書き換えることは出来ない。つまり以下のコードは通らない

auto sv = std::string_view(str);

sv[0] = 'a'; // エラー!書き換え不可能

std::optional

無効かもしれない値を表現するクラス、std::optional ができた。<optional> を include すると使える。

以下のようにして使う

#include <optional>

// ...

std::optional<int> f(int x) {
    int* res = hoge::proc(x); // ポインタを返す外部の関数
    if (res)
        return *res;
    else
        return std::nullopt;
}

以下のような事もできる

std::optional<int> opt = f();

std::cout << opt.value_or(42) << std::endl; // nullopt なら 42 として扱う

ようやく入った。しかし C++ なので opt.map(f) みたいなやつはない。

std::variant & std::visit

プログラムを書いているとたまに union を使いたい場面が出て来る。でも union は型の概念を破棄しているに等しいクソだった。

C++17 ではまともな union にあたる std::variant が追加された。これも <variant> ヘッダを include すると使える。

std::variant<int, double, std::string> var;
var = 42;
var = 5.0;
var = std::string("kitty on your lap");

var = 'a'; // これはエラー

値を扱うにはこうする

if (auto p = std::get_if<int>(var))
    std:cout << *p << std::endl;
else if (auto p = std::get_if<int>(var))
    std:cout << *p << std::endl;
else if (auto p = std::get_if<int>(var))
    std:cout << *p << std::endl;

しかしこれは流石に面倒なので std::visit() という関数が用意されている。

std::visit([](auto&& x) { std::cout << x << std::endl; }, var);

神的に便利になる。型で処理をスイッチしたい場合はそういうクロージャーを作ってそのインスタンスを渡してやれば OK。便利 (大事なことなので 2 回言いました)

std::any

上の 2 つは適当に機能を制限していたが、それをぶっちぎってなんでも入る型 std::any が追加された。<any> を include すると使える。

使い方は std::variant のもっと自由な感じなので省略。ただし値を取り出すときには std::any_cast() を使う必要がある。

初学者であるほど使いたがりそうだが、初学者であるほど使ってはいけない機能個人的 No.1。本当に必要な時に伝家の宝刀として。

ファイルシステムを扱う機能が追加

ファイルシステムを扱う機能群としてとうとう <filesystem> ヘッダが入った。また、この機能群はほぼ全て std::filesystem 名前空間上に定義される。

これに関しては 江添さんのやつ が非常に詳しい。というか僕には手に負えないので重要ではあるが存在の紹介にとどめさせていただきたい。

std::apply

std::visit() の tuple 版っぽい std::apply() が追加された。これで tuple の各要素に対する一様な操作が以下のように書ける。

std::tuple<int, double, std::string> tup = f();

std::apply([](auto&& x) { std::cout << x << std::endl; }, tup);

便利。

std::make_from_tuple

tuple の各要素をある型 T のコンストラクタに突っ込みたいときがあるが、今まではいちいち補助関数を書く必要があった。

面倒なので std::make_from_tuple() が追加された。

struct Hoge {
    Hoge(int, double, std::string); // 多変数を取るコンストラクタ
};

std::tuple<int, double, std::string> tup = f();

Hoge hoge = std::make_from_tuple<Hoge>(tup); // こんな感じ

連想配列系コンテナ関連

ノード単体を扱う機能がついた

  • std::mapstd::unordered_mapstd::multimap や対応する set 系コンテナに、その要素のノードを扱う型である node_type が追加された。
  • これは一見 iterator のように見えるが遷移操作ができず、単に要素自体を表す型になる。
  • これに合わせて、引数に渡した要素をコンテナから破棄してその node_type を取得する extract() メンバと、2 つのコンテナを連結させる merge() メンバが追加された

その他のメンバ追加

  • try_emplace()insert_or_assign() が追加された
  • try_emplace()emplace() と違って要素が新たに挿入されなくても引数に渡した変数がムーブされない
  • insert_or_assign()operator [] とほぼ同じだが、返り値が (要素の参照, 要素が追加されたかどうか) の pair になっている

<functional> 関連の色々

検索アルゴリズムの追加

  • <functional> ヘッダに検索アルゴリズムがいくつか追加された。
  • ボイヤー・ムーア法とかがある

std::not_fn

  • 「bool に変換できる型を返す関数の返り値を反転させる関数」を返す std::not_fn() が追加された
  • 名前が楽しくなさそうという印象が強い
  • 合わせて std::not1()std::not2()std::unary_negate() などは非推奨化された

std::invoke

  • 「関数を呼び出す関数」である std::invoke() が追加された
  • 嬉しい人には嬉しい

<type_traits> 関連の色々

std::void_t

  • テンプレート引数に何を渡しても void になる型、std::void_t が入った
  • 一見意味がなさそうに見えるが、SFINAE で便利になる。

変数テンプレートの追加

  • std::is_hoge<T>::value とか std::is_hoge<T>{} の代わりに std::is_hoge_v<T> と書けるようになった

std::invoke_result への移行

  • std::invoke_result<F, Args...> で、型 F の関数を型が Args... の引数で呼び出したときの返り値の型が取得できる
  • 元々 std::result_of があったが、ちょこちょこ問題があったためこれは非推奨化され、代わりに std::invoke_result が出来た

std::bool_constant

  • コンパイル時の型として区別できる bool 定数を表現するための std::bool_constant が入った
  • 今までなかったのが驚き

論理演算系の追加

  • 否定、論理積、論理和 などが入った
  • 例えば論理積なら std::conjunction<std::is_foo<T>, std::is_bar<T>>::value みたいな感じで書ける

その他にもなんか色々ある

メモリ系の色々

std::shared_ptr が配列に対応

  • タイトル通り。なぜいままでなかったのか

shared_ptr::weak_type

  • std::shared_ptr<T> のメンバ型に std::weak_ptr<T> に対するエイリアス、shared_ptr::weak_type が入った

memory_resource

  • メモリの確保/解放に関する新しい管理機能群が入ったヘッダ <memory_resource> が出来た
  • 要は次世代版アロケータ

メモリ管理機能機能の追加

  • <memory> に色々入った

キャッシュライン定数が追加

  • <new> ヘッダにキャッシュ効率を色々頑張りたい時用のコンパイル時定数、std::hardware_destructive_interference_sizestd::hardware_constructive_interference_size が入った

その他

重要なものも混ざっています

並列アルゴリズム

  • みんな大好き(?) <algoriothm> ヘッダの関数の並列化バージョンが入った
  • std::sort(std::execution::par, std::begin(vec), std::end(vec)) みたいにすると使える

コンテナに関するフリー関数の追加

  • <iterator> ヘッダに std::size()std::empty()std::data() が追加された
  • それぞれ(その機能が提供されていれば)配列を含めるコンテナの要素数、コンテナが空かどうか、要素の配列の先頭のポインタを取得する

std::scoped_lock

  • 今まで複数の変数を lock_guard するのは名前付けとかが面倒だった
  • C++17 では std::scoped_lock locks(x, y, z); みたいにすることで x, y, z 全てに対する lock_guard を取得できるようになった

std::shared_mutex

  • 書き込みが少なくて、読み込みが多い変数の mutex を効率的に行う shared_mutex が追加された
  • flag とかに使えそう(?)

std::as_const

  • const でない変数に const を一時的に付与したい時が割りとあるが、const auto& cx = x みたいにするか const_cast をするしかなかった
  • 不便なので <utility>std::as_const() が入った

std::clamp

  • <algorithm> ヘッダにある値を一定範囲に丸める関数、std::clamp() が入った
  • std::clamp(val, min, max) で値が [min, max] の範囲に入るよう丸められる
  • 数だけでなく一般に使えるので <cmath> じゃないっぽい

<cmath> ヘッダに色々入った

  • ベータ関数だのルジャンドル多項式だののすごい数学関数がいっぱい入った
  • 三次元版の std::hypot() とかも入った

std::gcd() & std::lcm()

  • std::gcd()std::lcm() が入った
  • <cmath> ヘッダではなく <numeric> ヘッダなので注意
  • 僕は性根が腐っているので早くこれに気付かずに死んでいく using namespace stder の顔が見たい

emplace 系関数の返り値が付いた

  • 今まで vector::emplace_back などの emplace 系関数の返り値は void だった
  • C++17 からはこれが挿入された要素の参照を返すように
  • つまり auto& back = vec.emplace_back(5); みたいな事が出来る

std::uncaught_exceptions

  • まだキャッチされていない例外の個数を取得する std::uncaught_exceptions が入った
  • 同時に std::uncaught_exception は非推奨になった
  • 名前の区別がつきにくくて困る

その他の非推奨になったもの

  • std::iterator
  • shared_ptr::unique()
  • <codecvt> ヘッダ
  • std::allocator の幾つかのメンバ

以上

どうでしょうか。色々抜けはあるかもしれませんが大体こんな感じだと思います。

個人的にはまあダメなところを上げれば concept がないだとか range がないだとか structured bindings が微妙だとか色々いえますけど、良く言えば「次に繋がるアップデート」になるかなと思います。

ただ地味に重大な問題として未だに C++17 を完全に実装したコンパイラが存在しない気がします。

というわけでみなさんも良き C++ ライフを。明日は I さんの「ネタはあるんですが,時間的余裕がない可能性が高く,万が一書けない場合は @azaika_ が代わりに」です!!!

DDCC2017 参加記

SuperCon で爆死して参加記を書いていなかったのでこっちを書きます。

DDCC is 何

ディスコ株式会社とディスカバリーチャンネルが共同で開催しているオンサイトのプロコン。Disco Discovery-channel Code Contest の略らしい(当日まで Disco Discovery Channel programming Contest だと思っていた)。

800人ちょい出た予選のうち上位の就活枠 100 人と一般枠 100 人が参加できる。オンサイトにしては緩めの予選。

ちなみに PCK の本選と同日に開催されていたが、PCK 予選落ちをキメていた僕には関係がなかった。

予選

AtCoder の Web サイト上で予選が行われ、それに参加した。難易度だけでいえば ABC くらいで、さらに D 問題が発想&場合分けみたいな感じだったので水色に優しかった。とはいえ僕は実装が遅れて 141 位くらいになり割りと不安だった。

100 位を下回っている場合就活枠が優秀な方が一般枠が通過しやすいので就活枠が優秀であることを祈っていた。結果、祈るまでもなく就活枠は優秀だったので通過することが出来た。

本選競技まで

大会側から「電源タップ少ないから必要なら早めに来て確保しろ」(意訳)みたいなメールが来たので早めに行かざるをえない感じになり、早寝早起きを目指したが早寝に失敗したので朝が辛かった。全部オデッセイとかいうやつが悪い。

そんなこんなで、眠い目を擦りながらなんとか受付開始前に会場に到着して完全に勝利したとか思っていたが、なんと前には既に結構行列が出来ていて悲しかった。

受付で T シャツを受け取り、会場へ向かう。糖分補給用のチョコレートを配っていたのでありがたく頂戴してからタップのある空席を確保して安心していた。

その途中で eiya さんと合流するもその他の知り合いが一切見つからなかったため一人座席で JOI の過去問を解いていた。その後で、そもそも知り合いは PCK 本選に行っていて DDCC には来ていないことに気づいて辛くなった。気づいてから競技開始までは某デッセイのせいで眠かったので半寝していた。

競技中~終わり

ちょくだいさんのトークの後に競技が始まった。配点が「300-500-800-1100-1300」なので素直に前から解こうとした。

初っ端の A 問題で詰まりかける。10 分くらい経ったところで愚直にやるだけすればいいことに気づいて AC。非常に教訓的だった。

A 問題で時間を食ってしまったため焦って B 問題を開く。LCM が大好きなのでこっちは一瞬でわかり即実装したが、何故かバグる。8 分位してから扱う変数は int64_t なのに作業変数だけ int にしていたことに気づく。直したら通る。ちょっと反省した。

C 問題を見る。グラフだったので図を書いてみるも全然分からない。使えそうなアイディアは出て来るが考察に結びつかない。そんなことをやっているうちにコンテストが終わった。順位表凍結時点で 88 位くらいだったので、これから下がりそうな事を考えれば微妙な感じ。悲しくなっていた。

ところが、その後誰に聞いても C 問題が解けたという人がいない。凍結時点で AC していた Olphe さんに聞いてみても「乱択は最高」(文意を捻じ曲げる意訳)みたいな事を言われる。その後おなじく凍結時点で AC していた Square さんに聞きに行こうとするも、自明解の説明までされたところで大会側からのインタビューが入り中断。その後 Square さんと会話できる機会はなく、結局 C 問題の解法は謎に包まれていた。

その後は振る舞われた昼飯を食べつつ出会い厨に勤しみ、何人かに出会えた。昼飯はパスタに茹でられていない部位が多々あった事を除けば結構豪華で美味しかった。

午後にはプロ棋士の方と Ponanza の作者様と chokudai さんの対談を楽しく見たり、皆が社内見学ツアーに行く中で申し込み忘れた僕は Rust で競プロしてみたりしていた。その途中で問題の解説もアップロードされ、C 問題の解説を見て「理解できない…」とか言っていた。

あとは懇親会とかあってから結果発表。僕は 90 位で結局微妙だった。結局 C 問題を解けたのが 30 人程度しかいなかったため順位変動もあまりなく早解きコンっぽかったので、それならもうちょっと上を目指したかった。

最後に DDCC 特製ケーキ(豪華&美味しい)が振る舞われたり、 chokudai さんと写真を取らせていただいたりして解散した。写真は家宝にしたいです。

Siv3DでDxLibのLoadDivGraphみたいなことをする

久々にブログを書く話題がこんな小ネタというのもなんですが、地味に求められているらしいので書いておきます。

DxLib の LoadDivGraph 関数

皆さんご存知 DxLib には、指定した画像を適当なピクセル数で分割して読み込む、LoadDivGraphという関数が存在します。
関数名がゴミとかそういう話もしたいのですがグッとこらえて先に進むと、どうも gif の代わりにアニメーション用の連続した画像を格納するときとかに便利らしく、Siv3D でもそんな関数がほしいという声が散見された[要出典]ので、前に書いた Siv3D での実装を載せたいと思います。

方針&ソース

Image を読み込んで、Image::clipped 関数で指定された大きさのピクセルを切り取っていき、それぞれをTextureに変換していく感じです。
こういうところで step(Size) とかが使えると気持ちいいですね。
ちなみにコメント中の RVO とは Return Value Optimization の略で、関数の戻り値のコピーを消す最適化のことです。C++17 からは絶対にこの最適化が行われるようになります(詳しくはこのへんとかこのへんを読んで下さい)。

// image を大きさ size の画像に分割

Array<Texture> makeDividedTexture(const Image& image, const Size& size) {

    // 分割数

    const Size numDiv{ image.size.x / size.x, image.size.y / size.y };



    Array<Texture> result;

    result.reserve(numDiv.x * numDiv.y);



    for (auto&& p : step(numDiv))

        result.emplace_back(image.clipped(size * p, size));



    // RVO で消える

    return result;

}

…とかいう事を書いている最中に、Siv3D の作者様から

このようなご指摘をいただきました。というわけで、特に「読み込んだ画像を編集したい!」のような場合以外は、TextureRegion を使用した

gist.github.com

(via Ryo Suzuki)

のような関数/コードを書くのが良いかと思われます。

結論

  • 昔書いた脳死コードを適当に人に上げない。
  • Texture の編集を特にしない場合、画像の一部分だけを扱いたいなら TextureRegion を使おう。

以上です。

Siv3Dにおいて一部ビルドでアライメントの関連のランタイムエラーが出たときの備忘録

Siv3DでQuaternionを扱う際にハマった場所があったので備忘録。

問題

問題は「Siv3DにおいてQuaternionなどをメンバに持つ派生クラス基底型のポインタに突っ込むとx86ビルドで死ぬ場合がある」というものです(自分はx86、Releaseビルドでのみ発生しました)。

具体的には以下の様な例です(このコードが死ぬ保証はありません)。

#include <Siv3D.hpp>
#include <memory>

class A {
public:
    virtual void draw() const = 0;
};

class B : public A {
public:
    void draw() const override {
        Plane(5.0, q1 * q2).draw(); // ここで死
    }

private:
    Quaternion q1 = Quaternion::Yaw(45.0_deg), q2 = Quaternion::Roll(30.0_deg);
    Plane p = Plane(10);
};

void Main() {
    std::shared_ptr<A> ptr;
    ptr = std::make_shared<B>();

    while (System::Update()) {
        ptr->draw();
    }
}

解決方法

あまりに不可解だったので、TwitterSiv3Dの作者さん にお聞きしたところ「アライメント関連の問題だと思われるので class B に alignas(16) を付けてみて欲しい」という回答をいただき、やってみたのですが、上手く動きませんでした。

しかし、その後std::make_sharedがアライメント指定されたメモリ確保に未対応なのではと思いあたり、アロケータを自作することにしました。
ここでは、Siv3Dにはアロケータを考慮したmallocであるAlignedMalloc関数が用意されていることを教えていただき、それを使うことで多分結構楽出来ました。

というわけで以下の様な関数を作ることで解決することが出来ました。

template <typename T, typename... Args>
auto alignsafeShared(Args&&... args) {
    auto* p = AlignedMalloc<T>(1);
    if (!p)
        throw std::bad_alloc();

    new(p) T(std::forward<Args>(args)...);
    return std::shared_ptr<T>(p, AlignedFree);
}

(ちなみにnew(p) Tの部分を*p = T()にしたら動かなかったので、理由が分かる方は教えて頂けると幸いです)
追記: Twitterで「Mallocだけでは仮想関数テーブルが生成されておらず、operator =の呼び出しで不正なメモリを参照してしまうから」と教えていただきました。ありがとうございます。

というわけで、上記の例にこれを適応させた最終結果がこうなります。

#include <Siv3D.hpp>
#include <memory>

class A {
public:
    virtual void draw() const = 0;
};

class alignas(16) B : public A {
public:
    void draw() const override {
        Plane(5.0, q1 * q2).draw();
    }

private:
    Quaternion q1 = Quaternin::Yaw(45.0_deg), q2 = Quaternion::Roll(30.0_deg);
    Plane p = Plane(10);
}

template <typename T, typename... Args>
auto alignsafeShared(Args&&... args) {
    auto* p = AlignedMalloc<T>(1);
    if (!p)
        throw std::bad_alloc();

    new(p) T(std::forward<Args>(args)...);
    return std::shared_ptr<T>(p, AlignedFree);
}

void Main() {
    std::shared_ptr<A> ptr;
    ptr = alignsafeShared<B>();

    while (System::Update()) {
        ptr.draw();
    }
}

ちなみに、現在開発中の OpenSiv3D にはこのalignsafeSharedと大体同じ機能を持つMakeSharedと、その生ポインタ版であるAlignedNewが用意される予定だそうです。

SuperCon 2016参加記

今年は幸運にも SuperCon 2016 の本選に参加してきたので、それの所感と経緯を書きたいと思います。

What is SuperCon

正しくは SuperComputingContest で、東工大大阪大学が共同で開催している大会で、SuperConを使って規模のデカイ問題を解こうぜ!
みたいな大会のはずです。

結果

とりえあず結果だけ先に書いてしまえば、全20チーム中12位という結果に終わりました。

覚悟していたよりは良い結果だったのですが。色々悔しさが残っています(後述)

予選

予選開始前

まずはじめに、僕はプログラミングを4年間やっているのに競プロができないという致命的な問題がありました。

どのくらいかというと、初めてdijkstraを書いたのがこの記事を書く1週間前くらい、というレベルです。

というわけで、最初は割と「何かの間違いで本選出れたりしないかな」みたいな気分でいました。

予選開始

そんな中、 予選問題 が公開されました。

ここで一応簡単に予選問題について解説すると、
6x6の魔法陣に15個の虫食いがあるからできるだけ高速に埋めてね
という問題です。

問題公表から申し込み締め切りまでは17日間あり、その2週間ちょいでコードを書く必要があります。

こんなシンプルな問題ですが、僕は最初パっと解法が思いつかずに悩んでいて、数日は放置気味にしていました。

しかしパートナーの方が解法への糸口を掴んでくれていたらしく、そちらに最初の実装をお願いしました。

予選おわりまで

そこから1週間目の終わりくらいまではパートナーから実装の解説を聞いておかしいところを話し合いつつ、僕は何もせずにパートナーが頑張ってくれていました。

ちなみにこの時点で大体まとまった戦略は
魔法陣の各列に対してその列で使う可能性のある数をbit列で保持しておき、それの論理積を取って各マスの候補をさらに絞り込んでから再帰で全探索
というものでした。

しかし、2週間目に入った時にパートナーの方から「実装が詰まったから手伝ってくれ」というヘルプが飛んできました。

ところが、ソースをもらったところ、コードがグチャグチャでまず問題が発生しているコードの位置を特定できなかったので、基礎の考え方はそのままで僕の方でコードを1から書き直すことにしました。

そんなこんなで2週目の土日までにはコードが完成しました、が、重いテストケースで実行時間が1時間を突破してしまい、これはアカンということで最後数日で最適化を始めました。

そこで、速くなりそうな部分を探したところ、各マスの候補から全探索を行う際に色々と無駄なループが走っていることがわかり、割と気合でコードを書いて対策しました。

すると、実行時間が軽いテストケースでは200μs、重いテストケースでも2~7ms以内に収まるようになり、希望が見えてきました。

しかしこのソース、本来大会側で用意してあるヘッダーで入出力と時間計測を行わなければいけないのですが、それを完全に見落としており、最後の最後で慌てて修正しました。

あとは適当にコメントつけたりしてから提出しました。締め切り前日だったので結構危なかったです。

一応書いたソースは ここ に載せておきます。

別の出場チームの話を聞いたり、Twitterを見たりしたら皆実行時間が短くて大分ヒヤヒヤしたり、予選の結果発表が伸びたのをネタにしたりしていましたが、無事予選通過することができました。

しかし、実は今回の SuperCon はJOIの夏季ゼミと日程が丸かぶりしていたために、昨年の優勝校含む強豪学校が SuperCon に参加しておらず、当初の考え通り「何かの間違いで通過」に近かったかもしれません。
どちらにせよ通過出来たことは単純に嬉しかったです。

予選までの所感

今思うと最初半ば諦めていたとはいえとりかかり始めるのが遅く、完成したソースも最適化が微妙でまだ結構定数倍高速化できる部分がありました。

あとは、SuperConは基本的にC言語オンリー & 実行がUNIX環境のため、Windows & C++ ゆとりの僕には大分辛かったです。このために仮想マシン上にUbuntuでテスト環境を構築するはめになりました。

このC言語制限は本選でも共通だったため、色々と消耗しました。

本選

最初に

SuperCon の本選は東は東工大、西は阪大に予選を通過した20チームが集まり、5日間にわかって問題を解く形で行われます。

いつもは東と西で10チームづつ選出されるらしいのですが、今年は前述の夏季ゼミと被ったこともあってか東側の募集が少なかったらしく、東が8チーム、西が12チームという配分に別れました。

というわけで僕たちは東8チームのうち1チームとして5日間東工大に通いました。このうち、問題を解くのは4日間で、最後の日は表彰式と懇親会でした。

1日目

1日目は午前に集合したのちに、偉い方の挨拶、各チームの紹介などがあり、午後から東工大のコンテスト環境や回答提出方法の解説、問題の発表が行われました。

東工大では端末として iMac が用意されており、OS XWindows がダブルブートできる環境だったのですが、本選では OS X の方を使用しました。

ここでマウスが Magic Trackpad で無かったり、OS X のバージョンがEl Capitan ではなく Mavericks だったりして高まっていたテンションが少し下がりました(これはむしろ良かったかもしれませんが)

本選問題は簡単にいえば
頂点がn個で次数がdの単純正則無向グラフの ASPL(総最短経路長) ができるだけ小さくなるように経路を求めよ という問題です。

これを各チームに割り当てられたスパコンのノードを自由に使って解くのですが、今回のスパコンには東工大TSUBAME が使用され、TSUBAME の特徴である GPU を CUDA C でいかに上手く動かすか、という感じになっていました。

一通りの説明が終わった後は、問題を解く時間となったのですが、まったく解法が思いつかなかったので補助用のシェルスクリプトを書いたりしてUNIXに慣れていました。

パートナーの方は何か解法を思いついたらしく、2人で相談しましたが、僕が上手く実装できる気がしなかったので、予選と同じく最初の実装をパートナーにお任せしました。

2日目

この日は朝の電車内で色々考えた結果、少し前に TSP について調べた時の手法が使えるのではないかという考えに至り、「ランダムでグラフを生成してから、更にグラフのランダムな2辺を取って入れ替える事を繰り返す」という手法を試して見ようと思いました。

2日目は15時から16時の強制休憩タイムを除き、9時から20時まで問題を解ける時間となっていましたが、この日でランダムなグラフの生成を書くことができました。

またランダムなグラフ生成における閉路判定に Uinon Find が必要だったのですが、僕はパッと書ける自信が無かったのでパートナーに書いてもらいました。
パートナーは競プロを比較的真面目にやっていて、データ構造に関する知識があったのでこういう場面ではありがたかったです。

この時点で、この手法に関して「これはイケる」という雰囲気を感じており、パートナー側と話し合って、2人で別の解法でプログラムを書こうという作戦にしました。

ちなみに、本番が終わった後に他のチームから指摘されて気づいたのですが、入力例に対するランダムなグラフの生成はサンプルプログラムとして大会側から配布されており、1日目にその説明もありました。
つまり、ランダムなグラフの生成は特に理由がなければ、手書きする必要がないようになっていたのです。

しかし、僕はそれを完全に失念しており、この2日目をグラフ生成に使ってしまいました。
これが最終日に時間が足りない直接の原因になり、今も凄まじく後悔している一番のミスです。

また、強制休憩タイムでは関西会場とつながったテレビ会議システム上でお絵かきしりとりをしていました。楽しかったです(小並感)

あとはGoogleの方からNougat入りのチョコレートの差し入れがあり、ありがたくいただいていました。

3日目

3日目も2日目と同じように、強制休憩タイム以外は進捗の時間として使えました。

この日は「2つのランダムな辺を選択して入れ替え、ASPLを計算した後に小さい方を取る」という部分の実装をしました。

ASPLを求める方法については事前に幾つか手法の紹介をされていたこともあり、この部分の実装は午前のうちに終わった…とおもいきや、ASPLの値が盛大におかしいことになり、昼ごはんを食べるまでずっと悩んでいました。

しかし昼ごはんを食べた後にすぐ、辺の入れ替え後に正しいグラフになっているかの判定でUnion Findを使っているのに、Uinon Find用のtreeが更新できていないことに気づき、修正が面倒くさそうなので手法を変えて実装しました。
食事は偉大です。

ちなみにこの「辺入れ替え後のグラフ判定」ですが、そもそも辺を入れ替えたあと閉路で無かった場合は ASPL が INF になるようになっているので、そもそも必要でないことに4日目で気が付きました。
食事は偉大でも、食べる側の人間の頭は偉大から程遠い場所にあったようです。

あとは、そもそも辺をswapする際の動作が間違っていたせいでinvalid graphになる問題なども発生して、午前に感じていた余裕は吹っ飛びました。

最後1時間位はASPLの計算に使用したフロイド・ワーシャル法の並列化方法が思いつかずに悶々としていました。

これに関しては家に帰ってからきちんと考えました。

4日目(午前)

4日目は、コーディングできるのは午前と本番である13時から15時の間のみでした。

朝になった時にパートナー側で試していたプログラムが結局ダメであった事を告げられ、僕のプログラム一本で行くことになりました。割とキレました。

しかし、前日の夜に足りない頭を絞った結果、フロイド・ワーシャル法の並列化がめちゃくちゃ楽な事に気づいたので、ぱっと実装…と言いたかったのですが、それまで全く CUDA に触れていなかったため、スレッドブロックの割り当てなどでちょっと手間取りました。

更に、これで完成…と思いきや、フロイド・ワーシャル法の後の配列の総和を求める部分をCPUでやると、メモリ転送コストで時間がCPU実装とそんなに変わらないという事になってしまっていました。

というわけでGPU側で配列の総和を求めるプログラムが必要になったのですが、これも大会側がCUDA向けのサンプルとして用意してくれていましたので、それを使おうとしました。

しかし、この総和の算出部分がなぜか上手くいかず、謎を抱えたまま昼になってしまいました。

本番

昼の後、本番がはじまりました。
本番は、入力が9個与えられ、それぞれの入力に対するできるだけASPLが小さい経路を2時間の間に求めて提出する形式で、その2時間の間はプログラムの修正が自由です。

CPUのみを使うプログラムではASPLの算出に時間がかかり、与えられた頂点数が大きい場合、辺の入れ替えを効果がでるまで繰り返す事ができないのがわかっていたので、パートナーにとりあえずの提出を任せ、僕はGPUでの総和計算でのバグを治すことに全力を注ぐことにしました。

しかし、結果だけ言ってしまえば、総和計算のバグを取ることは出来ませんでした。
このバグの原因は未だにわかっていません。

CPUの方でも超点数が少ない場合は試行回数を増やせるので、かなり手応えのある結果を出せる事が確認済みだったのですが、運の悪いことに、本選の問題の多くは頂点数が大きめに設定されており、最終的には全く良い結果が出せませんでした。

更にその後に2日目のミスに気づき、パートナーとともにただひたすらに後悔していました。

本番後は大会側から各チームへのインタビューがあり、チーム名の由来やメンバーの得意技を、問題を解いたアルゴリズムを聞かれました。
得意技に関しては、全く思いつかなかったので「OSデータ破損芸」と言ったらそのまま採用されてしまいました。

5日目

5日目は表彰式と懇親会でした。

表彰式ではまず主催者やスポンサーからの挨拶、再度各チームの紹介などがあり、そこから結果発表と入賞チームや奨励賞受賞チームへの賞状や副賞の授与がありました。

結果発表に本番2時間中の各入力での5位までの順位の変動がわかる動画が使われており、面白いなと思って見ていました。僕らのチームは一回も動画の中に出てきませんでした。

最終的に僕らの結果は上記の通り12位でした。

その後は懇親会があり、じゃんけん大会をしたりしました。
本番で使われなかった運をじゃんけん大会で使った気がする程度には勝ち、いくつか景品を戴きました。

あとは部屋でダベってから帰って爆睡しました。

全体を通しての感想

予選でも本選でもそうですが、配布されたものをしっかり確認しなかった結果、丸々1日損する事になったので、もっと気をつければよかったというのが一番の反省ポイントです。

あとは、慣れないシェルスクリプトVimC言語 & CUDAについてチューターさんに質問を大量に投げてしまったのですが、毎回しっかり回答をいただけたのは非常にありがたかったです。

まああとはGPUの総和バグの原因が未だに分かっていないのが気がかりです。家にCUDAを実行できる環境がないのでもう直せないのがなおさらですね。

とりあえず、プロは強いということが実感できたので、来年も本選出場してもっと上を目指したいです(KONAMI)

Siv3D上で動作するプロセス間通信ライブラリを作った

作っているものの関係でSiv3Dで動作するプロセス間通信ライブラリを組んだので、それの使い方とか概要を書いておく記事。

概要

名前はAzaika siv3D extention library、頭文字を取ってAselです。simple is best。
今のところ、他プロセスの制御とプロセス間通信機能があります。
しかし、他プロセス制御部分はちゃんと動いてないっぽいので、今回はプロセス間通信の方について書こうかと思います。

使い方

まずgithubからソースを落とします。
そしたら落としたソースの中にあるファイルを適当な場所にコピーし、Aselフォルダ内のcppを全てプロジェクトに追加します。
あとはAsel.hをincludeすれば使えるようになります。

使い方は適当なコードを見たほうが速いと思うのでそうします。
とりあえずサーバー側のコード

#include <Siv3D.hpp>
#include "Asel.h"

void Main() {
    const Font font(30);
    const String sendText = L"Kitty on your lap";
    bool hasSent = false;

    asel::Server server(L"aselTestServer");
    if (server) {
        server.start([&](asel::File& pipe) {
            pipe.write(sendText);
        });
    }

    while (System::Update()) {
        if (server) {
            font(L"Server: \n" + server.getName() + L"\nの構築に成功").draw();

            if (hasSent || server.hasConnected()) {
                server.update();
                hasSent = true;

                font(L"テキスト: " + sendText + L"\nを送信しました").draw({ 0, 170 });
            }
            else
                font(L"クライアントからの接続待機中").draw({0, 170});
        }
        else
            font(L"サーバーの構築に失敗").draw();
    }
}

そしてクライアント側のコード

#include <Siv3D.hpp>
#include "Asel.h"

void Main() {
    const Font font(30);
    String readStr = L"サーバーへの接続に失敗";
    auto client = asel::connectServer(L"aselTestServer");

    if (client) {
        auto str = client.read(17);
        if (str)
            readStr = *str;

        client.close();
    }

    while (System::Update()) {
        font(readStr).draw();
    }
}

こんな感じです。
コードだけで大体の流れは掴めるかと思います。
実行してみれば更に流れが掴めると思います。

…なんていう適当な紹介はこれまでにして、もうちょっと詳しく見ます。

まずサーバー側の

server.start([&](asel::File& pipe) {
    pipe.write(sendText);
});

この部分。
Server.start()は引数にasel::File&型を取り、voidを返す(何も返さない)関数を引数に取ります。ここでラムダ式を使うと便利ですね。
引数に渡した関数は、クライアントからの接続があるときにServer.update()すると呼ばれます。
asel::File型については後述。

次にクライアント側の

auto client = asel::connectServer(L"aselTestServer");

この部分。
asel::connectServer()は引数で渡された名前のサーバー(内部的にはパイプ)に接続し、asel::Fileを返す関数です。

さて、この2つに出てきたasel::File型というのは、誤解を恐れずいうとServerが持つ内部的なパイプの実体(のインターフェース)です。
asel::File型にはFile.read(int size)File.write(const String& str)関数が用意されており、
File.read(int size)はsize文字分の文字列を読み込みOptional<String>を、
File.write(const String& str)はパイプにstrを書き込みboolを返します。

これが大体の基本です。

しかし、一つ注意しなければならない点があります。
実行してみた人は気づいたと思いますが、asel::Serverコンストラクタasel::connectServerの引数に渡したサーバーの名前というのは簡略化されたもので、WinAPIの仕様上実際には先頭に\\.\pipe\のような文字列が付加されます。
この辺については色々と用意してあるので適当に使ってみてください。

また、ライブラリで用意されているほとんどの関数にはXMLコメントで解説を書いてあります。
ちょっと分からなくなったら見てみて下さい。

最後に

という感じです。

また、このライブラリはまだバグがあったり、仕様が甘かったりする点が大量にあると思うのでそういうのを見つけたらIssuesに上げるか作者Twitterまで言って(そしてあわよくばpull-reqを)下さい。多分直します。

というわけでオレオレライブラリの紹介でした。
もうちょっとだけオレオレライブラリの宣伝をすると、これをWinAPIでも使いたい人はwawlというWinAPIラッパライブラリを公開していますので使ってみてください。