2020年12月29日火曜日

【技術書典10】「詳解 Integromat iPaaS 完全入門」の販売を開始しました

 こんにちは。
表題の通り、技術書典10 で「詳解 Integromat iPaaS 完全入門」の販売を開始しました。


Integromat は IFTTT や Zapier などと同じ iPaaS で様々なサービスと連携して自動化ロボットを作れるサービスです。
本書では Integromat の基本機能の解説と連携サービスから一部をリストアップしています。

Integromat は私自身お気に入りのサービスなのでユーザーが増えると嬉しいなと思っております。
RPA/iPaaS の入門の一冊として検討していただけたら幸いです。

本書は GitHub にてコンテンツ以外を公開しております。
本書へのフィードバックはこちらへお願い致します。

https://github.com/srz-zumix/book-integromat


2020年12月22日火曜日

[C++] Clang ではオーバーロードされた private 関数に明示的な実体化時のアクセスができない?

※この記事は C++ Advent Calendar 2020 22日目の記事です。

はじめに

過去に本ブログでも紹介した template の明示的実体化時に private メンバーアクセス可能な仕様を利用した private メンバー変数・関数へのアクセスですが、最近それを使っていて clang だけとある条件でアクセスできないことに気づきました。

この挙動が仕様として正しいのか正しくないのかは筆者ではわからないので、実挙動ベースの話になってしまいますことをご了承ください。

過去の記事

ブログズミ: [C++] Private な関数のテスト
ブログズミ: [C++] 本当に private なところ

サンプルコード

こちらは iutest の private メンバーのテストのための機能を使ったサンプルです。
Wandbox 用に圧縮したソースコードを使ってますので、実装を確認したい場合はこちらを御覧ください。
https://github.com/srz-zumix/iutest/blob/master/include/iutest_prod.hpp#L62

#define IUTEST_USE_MAIN
#include "iutest.hpp"

class A
{
public:
    int GetX(void) { return m_x; }
private:
    int m_x;
};

IUTEST_MAKE_PEEP(int A::*, A, m_x);

IUTEST(Peep, Test1)
{
    A a;
    IUTEST_PEEP_GET(a, A, m_x) = 42;
    IUTEST_EXPECT_EQ(42, a.GetX());
    IUTEST_EXPECT_EQ(42, IUTEST_PEEP_GET(a, A, m_x));
}

IUTEST(Peep, Test2)
{
    A a;
    IUTEST_PEEP(A, m_x) x(&a);
    x = 54;
    IUTEST_EXPECT_EQ(54, a.GetX());
    x += x;
    IUTEST_EXPECT_EQ(108, a.GetX());
    IUTEST_EXPECT_EQ(108, x);
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ
https://wandbox.org/permlink/2ZPvpUz1zDe1KHHs

ポイントは IUTEST_MAKE_PEEP と IUTEST_PEEP_GET です。
IUTEST_MAKE_PEEP で private メンバーアクセスするための情報をセットアップし、IUTEST_PEEP_GET で実インスタンスの private メンバーへの R/W を実現しています。

問題のコード
class Hoge
{
    int x;
public:
    Hoge() : x(42) {}
private:
    int GetX() { return x; }
    
private:
    int gX() { return x; }
    int gX() const { return x; }
    int gX(int) { return x; }
};


IUTEST_MAKE_PEEP(int (Hoge::*)(), Hoge, GetX);
IUTEST_MAKE_PEEP(int (Hoge::*)(), Hoge, gX);

int main()
{
    Hoge hoge;
    std::cout << IUTEST_PEEP_GET(hoge, Hoge, GetX)() << std::endl;
    std::cout << IUTEST_PEEP_GET(hoge, Hoge, gX)() << std::endl;
}

Clang の場合はエラー
https://wandbox.org/permlink/roGkNKKwsnaLX7og 
GCC の場合は OK
https://wandbox.org/permlink/AWd5A19WWDznccfq

エラーとなるのは gX 関数の PEEP で、オーバーロードされた関数があるとダメなようです。
Visual Studio はバージョンによって static メンバー関数がダメだったりするのは認識してましたが、 clang でもうまくいかないケースがあることがわかりました。

この挙動に関してなにか進展があったら、追記したいと思います。

では。


2020年12月14日月曜日

[DockerHub] AutomatedBuild でバージョンタグを自動でつける

DockerHub のイメージを見てるとタグがいっぱいついてるリポジトリがありますよね。
あとは latest が最新バージョンのタグと同じになってたり、Ubuntu の xeinal -> 16.04 みたいに名前付けされてたり、バージョンがメジャー、マイナーまでになっていてそのバージョンの最新を指していたり(例: v1 -> v1.3 -> v1.3.2)、とか。



こういうのを AutomatedBuild 管理するのは面倒くさそうだなと思ってましたが、そんなことはありませんでした。

hooks で AutomatedBuild をカスタマイズできる
DockerHub AutomatedBuild では Dockerfile と同じディレクトリに hooks ディレクトリを作成し、post_build や post_push ファイルを配置することでビルドをカスタマイズできます。カスタマイズできるポイントは↑の公式ドキュメントを参照してください。

post_build や post_push では bash スクリプトを書くことができ、docker コマンドも使用できます。ビルドしたイメージを実行したり、AutomatedBuild で設定したタグ以外をつけたりできます。

バージョンタグを自動でつける
post_build のタイミングでビルド済みのイメージを実行し、バージョンを取得すればタグ付けが自動化できます。
 
#!/bin/bash
# post_build
VERSION=$(docker run --rm ${IMAGE_NAME} --version)
docker tag ${IMAGE_NAME} ${DOCKER_REPO}:${VERSION}
docker push ${DOCKER_REPO}:${VERSION}
最後に
とあるツールをインストールした Docker イメージを作ることがちょくちょくありますが、ここれからは hooks 使って管理楽ちんにしたいと思います。

ではでは。

2020年12月8日火曜日

Zapcc を使った複数プログラムの並列ビルドでハマった話

※この記事は C++ Advent Calendar 2020 8日目の記事です。

Zapcc とは

zapcc は clang ベースのキャッシュを利用した高速な C++ コンパイラーです。
zapcc はサーバープロセスのメモリ上にキャッシュを構築して高速化します。
ヘッダーファイルの解析結果や template のインスタンス化などがキャッシュされます。

プリコンパイルヘッダーを使うよりも生成されたコードもキャッシュされるので高速です。
類似のツールで ccache というキャッシュツールがありますが、こちらは翻訳単位ごとにキャッシュする仕組みなのでフルビルドのときはキャッシュ生成するだけでキャッシュの恩恵を受けられませんが、 zapcc の場合は同じヘッダーファイルを include してるファイルで、template インスタンス化などのキャッシュが利用されるためフルビルドでも高速になります。

また、zapcc サーバープロセスがいる間は別のプログラムのコンパイル時でもキャッシュ利用可能なので複数のプログラムをビルドする際も高速になります。

導入は簡単でビルド・インストール後、 CXX=zapcc++ とするだけで使えます。
自作の C++ テスティングフレームワーク iutest のテスト環境として zapcc インストール済みの Docker を用意してるので、よろしければそちらからでも試せます。
https://hub.docker.com/r/srzzumix/zapcc

ハマったこと

zapcc はビルドを高速に行うためにキャッシュをサーバーに蓄えます。
キャッシュはサーバープロセスが生きてるうちは有効です。
つまり、1つの実行ファイルをビルドするときだけでなく、その後に別プログラムをビルドする場合にもキャッシュが有効になります。
ライブラリの大量にあるテストプログラムをビルドするときには、このキャッシュがとても有効に働くと思います。

筆者が開発している iutest でも、複数のテストプログラムがあるのでビルド時間が短縮されるのを期待しましたが、結果は「テストの失敗」でした。

何が起きたか。

ビルドは特に問題なくできているように見えました、しかしながらテストは失敗している。
どうやらテストが失敗するのは、デフォルト設定ではなくテスト用に機能の有効化・無効化をしているテストでした。
iutest はコンフィグマクロを定義することで任意の機能を有効にしたり、無効にしたりするのですが、これが zapcc と相性最悪でした。。

どうやら、以下のようにとあるテストでコンフィグを変えても、キャッシュされたヘッダーファイルを利用してしまうため、#define が伝わっていないことがわかりました。

#define IUTEST_HAS_VARIADIC_TEMPLATES   0
#include "iutest.hpp"
  
対策

並列ビルドしているとキャッシュされたヘッダーを利用してしまうため、直列にしました。 -j4 とかせず -j1 を指定。
はい。もこの時点で Zapcc の恩恵を捨てています。いやむしろビルド時間的には悪化する場合も考えられます。。。

次に、キャッシュサーバーのプロセスが生きたままになっているので、1プログラム作る度に kill するようにしました。。。

Makefile はこんな感じになりました。

ifeq ($(CXX_NAME),zapcc++)
BUILD4ZAPCC=pkill zapcc++; sleep 1
endif

$(TARGETS1) : $(OUTDIR)/% : %.cpp $(IUTEST_HEADERS) $(MAKEFILE)
	$(BUILD4ZAPCC)
	$(CXX) $(IUTEST_INCLUDE) $(CXXFLAGS) -o $@ $< $(LDFLAGS)

なんか無理くりですが、 iutest はビルド速度改善を目的に zapcc を使っているわけじゃなく、zapcc コンパイラで不具合がないか検証するために使っているので、これでもまぁいいのです。。

最後に

zapcc はすごいけど万能ではなかった。



2020年12月1日火曜日

[C++] Concepts で外部ライブラリの template よりも優先されるオーバーロード関数を書く

※この記事は C++ Advent Calendar 2020 一日目の記事です。

はじめに

 C++20 で Concepts が導入されました。
コンセプト - cpprefjp C++日本語リファレンス

コンセプトはテンプレートパラメータを制約する機能で、「コンセプトによって関数オーバーロードやテンプレート特殊化」ができます。
今までテンプレート関数のオーバーロードを書く場合は SFINAE などのテクニックを使って書いていたと思いますが、コンセプトで書きやすくなりました。
これも大きな変化の1つですが、コンセプト関数が通常の関数よりも優先して lookup されるのも便利になった点の1つです。
コンセプトによるオーバーロードの場合、コンセプト関数が通常の関数よりも優先して lookup され、要件を満たすコンセプト関数がなかったら他の候補を探しに行きます。SFINAE では優先順位がつけられないケースでもコンセプトなら可能になりました。

どういうこと?
SFINAE では解決できなかった優先順位問題とコンセプトを使った実装の例

↑こちらは、std::cout << できない型の場合は "unknown" を出力するようにしようとしている様子です。

donot-change.h には std::cout するだけの template 関数 print があります。
こちらは変更ができない想定です。(外部ライブラリとか)

SFINAE.cpp では頑張って、 std::cout ができない場合で特殊化しオーバーロードしようとしてます。他の書き方を書いたりもしましたが、どうしても ambiguous になってしまって条件を満たすことができませんでした。

一方 Concepts.cpp は printable ではないコンセプト制約でオーバーロードするだけで実現できています。
すごく簡単ですね。

ちなみに
ちなみに donot-change.h を変更可能とした場合は、以下のように書くことができます。
namespace printer_internal
{
template<typename Elem, typename Traits, typename T>
::std::basic_ostream<Elem, Traits>& operator << (::std::basic_ostream<Elem, Traits>& os, const T&)
{
    return os << "unknown";
}
}

// 解決順序
// foo::operator <<
// ::operator <<
// ::printer_internal::operator <<
template<typename T>
void print(const T& val)
{
    using namespace ::printer_internal;
    ::std::cout << val << ::std::endl;
}
これは ADL の解決順序を利用して実現しています。
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ https://wandbox.org/permlink/rlQoH37cvYTI23qo 
使い所は?
例でも示したとおり、オーバーロードしたい対象の関数を変更できるのであれば、そちらで解決する方法を模索するのが良いと思います。
ただ、外部ライブラリなど変更することができない場合、非侵入的に解決する方法として覚えておくと役に立つ日が来るかもしれません。

実例
そもそも私がなんで外部ライブラリの挙動をオーバーロードで変更したいと思ったのかというと、以下の理由でした。

* Google Test はアサーションが失敗すると、そのときの値を出力します
* std::cout できない場合はバイト列を出力します
* できない場合の対応は「ちなみに」の ADL を利用した方法で実現されています
* 古い Google Test ではこの機能がない
* 自作テスティングフレームワーク iutest は Google Test と互換性がある
* iutest は Google Test の拡張機能としての一面もある
* 古い Google Test と iutest の組み合わせでも不明な型を printable にしたい
* Google Test 側のコードは書き換えられない
* どうしよう

実際にコンセプトで対応してる部分はこちらになります。
最後に
教えていただいた いなむのみたま(@mitama_rs) 先生ありがとうございました。
ちなみにそのツイートにもぶら下がってますが、C++20 未満のコンパイラでこの問題を解決する方法も募集しておりますmm