追記:リテラル数値について

ところで、昨日の記事で、以下のコードはコンパイルエラーになって面倒だという話をした。

template<typename T>
T f(T x, T y)
{
    return x + y;
}

auto z = f(1.0f, 2.0); // f(float, double)

一応追記しておくと、これはtemplateだから生じる問題とかではなくて、オーバーロードでも普通に生じる。

double f(double x, double y)
{
    return x + y;
}
float  f(float  x, float  y)
{
    return x + y;
}

auto z = f(1.0f, 2.0); // f(float, double)

もちろん、理由はambiguousだからだ。

prog.cc: In function 'int main()':
prog.cc:14:25: error: call of overloaded 'f(float, double)' is ambiguous
     auto z = f(1.0f, 2.0);
                         ^
prog.cc:3:8: note: candidate: 'double f(double, double)'
 double f(double x, double y)
        ^
prog.cc:7:7: note: candidate: 'float f(float, float)'
 float f(float x, float y)
       ^

一般に、浮動小数点数リテラル値を書くときは色々と面倒が生じるということになる。

一度明示的に定義した型で受けてしまえば、この手の問題は発生しない。

float x = 1.0 / w;
float y = 2.0 * w;
auto z = f(x, y);

冗長ではあるが。

リテラル数値と精度について

背景

C++では1.0とか書くとdoubleとして解釈され、floatにするには1.0fなどと書かねばならないことはご承知のとおりだ。また、doublefloatの演算の結果はdoubleになる。

これはtemplateを使うときに若干面倒になる。例えば、以下のような関数fを実装したとする。

template<typename T>
T f(T x, T y)
{
    return x + y;
}

さて、浮動小数点数型を受け取るクラステンプレートがあり、中でfを呼ぶとしよう。

template<typename T>
struct X
{
    T g(T x) {return f(x, 2.0 * x);}
};

int main()
{
    X<float> x;
    std::cout << x.g(1.0f) << std::endl;
    return 0;
}

何が起きるだろうか。

答:コンパイルエラーになる。

なぜかというと、2.0 * xdoublefloatの積なのでdoubleになり、するとtemplate型推論が上手く行かない。というのも、

template<typename T>
T f(T x, T y)

に対して

f(float, double);

が与えられるのだ。Tはどちらとも取れない。

template argument deduction/substitution failed:
note:   deduced conflicting types for parameter 'T' ('float' and 'double')
     T g(T x) {return f(x, 2.0 * x);}
                      ~^~~~~~~~~~~~

という感じになる。

templateクラスの中で書かれてるリテラルT型に勝手になってくれよ〜と思うかも知れないが、T型とリテラルが同じ型になるという関係はこのコードを書いた人間の脳みその中にしかないので、コンパイラはそんな勝手な解釈をすることができない。

template<typename T>
struct X
{
    T g(T x) {return f(x, 2 * x);}
};

これなら通る。2は整数なので、演算結果は浮動小数点数になる。

問題は整数では表現出来ないときで、これは難しい。例えば、倍にするのでなく2.5倍したいときはどうするか。

ナイーブにやると以下のようになる。

template<typename T>
struct X
{
    T g(T x) {return f(x, 2.5 * x);}
};

これだと2.0の時と同じ問題が発生する。

それを避けるため、明示的にキャストするという手はある。

template<typename T>
struct X
{
    T g(T x) {return f(x, static_cast<T>(2.5) * x);}
};

単純に長い。一つならまだ良いが、複数個こんなものを書くとしんどい。static_castでなくT(2.5)と書いてもまあ良いのだが……

template引数の推論に失敗するのだから、それを明示的に与えればよい。

template<typename T>
struct X
{
    T g(T x) {return f<T>(x, 2.5 * x);}
};

これはまあすっきりしている。

他には、定数を集めたクラスを作っておいて、そこから持ってくるという戦略もあり得る。

template<typename T>
struct constant
{
    static constexpr T two_and_half = static_cast<T>(2.5);
};

template<typename T>
struct X
{
    T g(T x) {return f(x, constant<T>::two_and_half * x);}
};

果たしてこれでどれほど簡単になるのかという話だが。長くなると面倒な感じになっていくし、非常に冗長だ。piとかroot_piみたいな特殊な値を持っておくなら、この戦略はありなのだが(boost::math::constantsなどは似たようなことをしている)。

この例と同様のことは、例えば以下のような関数でも生じる。

template<typename T>
std::complex<T> f(T a, std::complex<T> b);

これをfloatで実体化しつつ、a2.0bstd::complex<float>とかを入れた場合、こうなる。

prog.cc: In function 'int main()':
prog.cc:11:50: error: no matching function for call to 'h(double, std::complex<float>)'
     auto c = h(2.0, std::complex<float>(1.0, 1.0));
                                                  ^
prog.cc:4:17: note: candidate: 'template<class T> std::complex<_Tp> h(T, std::complex<_Tp>)'
 std::complex<T> h(T a, std::complex<T> b)
                 ^
prog.cc:4:17: note:   template argument deduction/substitution failed:
prog.cc:11:50: note:   deduced conflicting types for parameter '_Tp' ('double' and 'float')
     auto c = h(2.0, std::complex<float>(1.0, 1.0));
                                                  ^

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

エラーの内容は同じで、型推論の結果doublefloatかわからなかったよ、という話だ。

速度の話

と、さてここまでで大体めんどくさそうだなあということはわかっていただけたと思う。

面倒くさいだけならまだ良いのだが。以下のリンク先を見て欲しい。

godbolt.org

このリンク先はwandboxと同じくオンラインコンパイラなのだが、アセンブリを見せてくれるところが違いだ。アセンブリC++コードの対応する箇所をハイライトしてくれたり、色々便利だ。

そこで、以下のようなコードをコンパイルした。

template<typename real>
real f(real x)
{
    const real cos1 = std::cos(x);
    const real cos3 = cos1 * (4.0 * cos1 * cos1 - 3.0);
    return cos1 - cos3;
}

同じことをする関数を3つ書き、それぞれ、リテラル4.04.0f4と変えていって、doublefloatで実体化した。ここに載せているのはgcc-8.2.0 -std=c++11 -O3 -ffast-mathの結果だが、clang-6.0.0でも基本的に同じことが起きた。

特筆すべきは、doubleリテラルを使ってfloatで実体化したときだ。コンパイラフラグは-ffast-math-O3を立てているので、リテラルfloatに再解釈されることを期待していた。しかし、現実はこうだ。

float f<float>(float):
  sub rsp, 8
  call cosf
  pxor xmm2, xmm2
  cvtss2sd xmm2, xmm0
  movapd xmm1, xmm2
  mulsd xmm1, xmm2
  mulsd xmm1, QWORD PTR .LC1[rip]
  subsd xmm1, QWORD PTR .LC2[rip]
  add rsp, 8
  mulsd xmm1, xmm2
  cvtsd2ss xmm1, xmm1
  subss xmm0, xmm1
  ret

ところで、cvtss2sdは"convert single single-precision floating point to single double-precision floating point"の略である。要するにfloatdoubleに変換するもので(singleが2回書かれているのは、SIMDという複数個一気に計算する命令があるから、それとの区別のためである)、cvsd2ssはその逆だ。

もう何が起きているかはわかるだろうが、もう少し追っておくと、mulsdは"multiply single double-precision floating point"であり、subsdは"subtract single double-precision floating point"の略だ。何が起きているかというと、こいつはfloatで来た引数を一度doubleにしてから計算して後でfloatに戻しているのである。

断っておくが、これはよくある「C言語ではfloatは必ず一度doubleにされてから計算されるからdoubleの方が速い」系の話の亜種ではない。これは現代においては殆どの場合嘘だ。K&R時代とか私は生まれてないぞ。現代ではちゃんと単精度用の命令があるので、floatの計算はmulssやらsubssやらで行われ、実行速度に特に差はない。SIMDとかなら差は出るが、その場合floatの方が速かったりする。

その辺りは詳しくは以下の記事などを参照していただきたい。

qiita.com

さて、対比のために単精度浮動小数点数リテラルを使った場合のアセンブリを見てみよう。

float g<float>(float):
  sub rsp, 8
  call cosf
  movss xmm1, DWORD PTR .LC3[rip]
  movaps xmm2, xmm0
  mulss xmm2, xmm0
  mulss xmm0, DWORD PTR .LC4[rip]
  add rsp, 8
  subss xmm1, xmm2
  mulss xmm0, xmm1
  ret

変換命令はないし、subssmulssが使われている。やはり、倍精度リテラルを使ったことで問題が生じたのだと考えるのが自然だろう。

まとめ

中に入れる型を決めないままコンテナだけを決める

背景

これはある実験機器から出てくる特殊なフォーマットの実験データを読むために作ったライブラリのために考えたやり方である。

そのデータは画像データとヘッダ情報から出来ており、基本的にはそれらのペアを返すことになる。だが数百KB〜数百MB程度と大きさが色々なので、メモリアロケーションの戦略を色々と変えたいことがあるだろうと考えた。

というわけで、read関数にオプションtemplate引数としてコンテナ+アロケータの種類をカスタマイズできるものを渡したい。だが、画像データとして生データを取得するか、ヘッダ情報から単位換算などを済ませたデータを倍精度・単精度浮動小数点数として読み込むかは変更できるようにもしたかった。すると、データクラスはそもそもデータ型が不定ということになる。その状況でどのようにしたらコンテナの種類だけ変えられるだろうか?

template<typename T, typename ???>
class ImageData
{
    using value_type = T;
    using container_type = ???;
    // 第二template引数で`container_type`を
    // `std::vector<T>`や`std::deque<T>`に変えられるようにしたいが……
};

解決策

みなさんはstd::allocatorのメンバ構造体rebindをご存知だろうか。細かいことを言うとC++17でdeprecatedなので知らなくても良いのだが、その場合はstd::allocator_traits::rebind_allocを見ていただきたい。これは異なる型を同じ戦略でアロケートするクラスを取り出すための型である。

例えば、連結リストは実際にはノードをアロケートする必要があるが、std::listではユーザーにノードの実装は見せていない。見せる必要もない。だが、アロケートの戦略は変えられるようになっている。すなわち、第二引数にAllocatorを取る。

namespace std
{
template<typename T, typename Alloc = std::allocator<T>>
class list;
}

アロケータには本当はstd::allocator<_Node<T>>などを渡したいわけだが、このノード型がどうなっているかはユーザーには不明である。なのでどうあれユーザーはstd::allocator<T>を渡すことになる。すると、std::listはどうにかして、ユーザー指定のアロケート戦略を尊重しつつ、_Node型をアロケートしなければならない。このために、allocatorrebindメンバを持っており、異なる型を同じ戦略でアロケートするクラスを取り出せるようになっているのだ。

何言ってんだお前という人のためにサンプルを載せておく。

template<typename T>
struct allocator
{
    template<typename U>
    struct rebind {typedef allocator<U> other;};
};

allocatorの中に構造体が定義されており、その構造体がtemplateになっている。これによって、typedef allocator<T>::template rebind<U>::other allocator_UのようにしてUのためのアロケータを取り出せるのだ。

この発想が転用できる。

まず、以下のようなクラスを用意しておく。

struct vec
{
    template<typename T>
    struct rebind
    {
        typedef std::vector<T, std::allocator<T>> other;
    };
};

struct deq
{
    template<typename T>
    struct rebind
    {
        typedef std::deque<T, std::allocator<T>> other;
    };
};

これが渡されてくる前提で、最初のクラスで以下のようにしておく。

template<typename T, typename tagT>
class ImageData
{
    using value_type = T;
    using container_type =
        typename tagT::template rebind<value_type>::other:
};

ユーザーが呼ぶための関数を用意する。

template<typename T, typename tagT = vec>
ImageData<T, tagT> read(std::string const& file_name);

template<typename T, typename tagT>
ImageData<T, tagT> read(std::string const& file_name, tagT);

カスタムする気のないユーザーは、普通にreadを呼べば良い。カスタムする気のあるユーザーは、テンプレート引数に渡すか、第二引数としてそのクラスを渡すとよいことになる。

const auto data = read<double>("sample.dat");

const auto data1 = read<double, deq>("sample.dat");
const auto data2 = read<double>("sample.dat", deq{});

さらに、先にユーザーにこのクラスが持っておくべきものを伝えておけば、ユーザーが作ったアロケータやコンテナすら射程に入る。

struct user_alloc
{
    template<typename T>
    struct rebind
    {
        typedef std::vector<T, user::allocator<T>> other;
    };
};

struct user_container
{
    template<typename T>
    struct rebind {typedef user::container<T> other;};
};

ユーザー指定のコンテナのインターフェースがstd::vectorなどと同じなら、ライブラリの実装は買える必要はない。

違う場合も、メンバ関数呼び出しをフックできるようにしておけば最小限の変更で対応できる。

例えばsize関数呼び出しには、以下のような関数を用意し、ライブラリ内部ではそれだけを使っておけば、自分でコンテナを作るレベルのユーザーはそれをフックして対応してくれるだろう。

// size() メンバ関数があればそれを呼ぶ
template<typename T, typename std::enable_if<
    has_mem_func_size<T>::value, std::nullptr_t>::type = nullptr>
std::size_t size(const T& container);

// sizeメンバがないコンテナを使うユーザーは、オーバーロードを追加する
template<typename T>
std::size_t size(const user::container<T>& c)
{
    return c.get_size();
}

他の解決策

ところで、ImageDataクラスはtemplate template引数をとるような実装もできる。

template<typename T, template<typename> class containerT>
class ImageData
{
    using value_type = T;
    using container_type = containerT<T>;
};

template<typename T>
using deq = std::deque<T>:

const auto data = read<double, deq>("sample");

この実装はシンプルだし、template引数だけで完結するなら何も問題はない。どうせstd::vectorより凄いコンテナを作ってくる人間は限界突破しているはずなので、この程度にはひるまないだろう。

唯一なにかあるとしたら、

const auto data = read<double>("sample.dat", deq{});

のような呼び出しが出来ないことだろうか。ディスパッチ用の何かは今回エイリアスなので、実体化出来ない。まあしかし、それだけだ。

微妙に便利なgccのオプション

普段アセンブリコードを眺めるときは、最初に覚えた方法であることと、たいてい同時に実行して確認しているのもあって、objdumpを使っている。

が、コンパイル結果のアセンブリコードを見たいだけなら-Sで十分である。

$ cat "double add(double x, double y) {return x + y;}" > add.c
$ gcc -S -O2 -c add.c
$ ls
add.c add.s
$ cat add.s
; ...
add:
.LFB0:
    .cfi_startproc
    addsd   %xmm1, %xmm0
    ret
    .cfi_endproc
; ...

そんなことは皆知っているのだが、GCC-を渡すと標準入力から入力を受け取ることもできる。

$ cat "double add(double x, double y) {return x + y;}" | gcc -S -O2 -c - -o add.s

このとき、出力ファイル名を指定しないと-.oみたいなファイルができて面倒なことになるので注意だ(消そうとしてrm -.oとかするとそういうオプションだと思われてエラーになるが、rm ./-.oとすると消せる)。

普段GCCは拡張子で言語を判断しているようなのだが、標準入力からコードを渡すと言語が判定できないと怒られる。 その場合 -x LANGUAGEとして言語を指定できる。LANGUAGEには、cc++objective-cobjective-c++goadaf77、などなどが指定できる。

ところで、gccで何らかのオプションをつけた時にどのようなマクロが定義されるかなどに興味のある人は多いだろう[要出典]GCCにはプリプロセッサデバッグするための機能があり、-dMとするとdefineされているものを全てダンプしてくれる。

これを使えば、例えばSIMD命令が使えるはずのCPUで__AVX2__のようなマクロが指定されるかどうか調べることができる。

$ gcc -O2 -march=native -mtune=native -dM
gcc: fatal error: no input files
compilation terminated.

おっと、そもそも入力ファイルがないと怒られてしまった。 こういう時の-だ。プリプロセッサだけ見たいのでファイルの内容なんぞどうでもよいのだから、/dev/nullをリダイレクトすればよいだろう。gccが混乱しないように-xcだけつけて……

$ gcc -O2 -march=native -mtune=native -dM -xc - < /dev/null
(.text+0x20): undefined reference to `main'
collect2: error: ld returned 1 exit status

おっと、mainがなかったのでリンクエラーになってしまった。どうやら存在しないファイルをちゃんとコンパイルしようとしてしまっている。 こういうときはプリプロセッサだけしか実行しないオプション、-Eの出番だ。普段はプリプロセッサマクロのデバッグに使うが、こういう時にも使える。

$ gcc -O2 -march=native -mtune=native -dM -E -xc - < /dev/null
# ...

あとは、想定している命令セットが検出されたかどうか、grepでもすればよい。

$ gcc -O2 -march=native -mtune=native -dM -E -xc - < /dev/null | grep AVX
#define __AVX__ 1
#define __AVX2__ 1

reluを分岐無しで実装する

ReLUとは、ご存知の通り、xが正の値ならxを返し、そうでなければ0になるという非常に単純な関数である。だがこれが深層学習において重要な役割を演じたのだから面白い。バックプロパゲーションをする時に微分係数が1より小さいよくある活性化関数だと指数的に係数が弱まって深層にすると学習できなくなるとか何とか。

同僚とReLUの話題になった時、私の頭には分岐無しで abs を実装するという話(『ハッカーのたのしみ』参照)が浮かんだ。現代的なプロセッサでは、分岐予測が働くので、予測を外した場合分岐は時間のかかる命令になる。なのでよく呼ばれる簡単な関数では、分岐を減らすことには十分な意味がある。 abs では何が行われていたかというと、bit演算をうまく組み合わせて一度も分岐をすることなく abs 関数を実装していたのだ。負の値だと最上位ビットが1になるがそうでなければ0であるという事実をうまく使って。

それが脳裏にあったので、ReLUも同様にbit演算を使えば分岐無しで実装できるのではないかと思った。

という訳でチャレンジしよう。まず、IEEE754を思い出すと、浮動小数点数は最上位ビットが符号ビットだ。なので負なら最上位が1、正なら0になっている。これは使える。というのも、int32_tにして算術右シフトを31回行えば、正なら 0 、負なら 0xFFFF.... の値になるからだ。

float x = 1.0;
std::cout << std::hex
    << (*reinterpret_cast<std::int32_t*>(x) >> 31) << std::endl;
x = -1.0;
std::cout << std::hex
    << (*reinterpret_cast<std::int32_t*>(x) >> 31) << std::endl;

続いて、これをうまく使って正負どちらの場合でも望みの結果が出るようなbit演算を考えたい。まず、負なら0になってほしいが、ゼロは指数部、仮数部共に0であるので、要するに全てのビットが0である(負の0は考えないことにする)。これを作るのは簡単そうだ。なにせ全てのビットが1の値を作ることができるので、例えば算術右シフトを31回行った結果とのbitwise orを考えよう。これだと、片方が1なら(負の場合)もう片方に関係なく1になる。片方が0なら(正の場合)もう片方と同じ値が出てくる。

筆算で書くとこうなる(便宜的に8bitとする。sは符号、eはexponent、fはfractional)。

    正の数    | 負の数
    seeeffff | seeeffff
    00101001 | 10101001
bor 00000000 | 11111111
------------ | --------
    00101001 | 11111111

負の数が全て1になり、正の数は保たれている。いい感じだ。

だが、負の数の場合に欲しい値は0で、そのbit表現は全てのbitが0というものだ。なのでbitを反転しなければならない。ただし、正の数の場合は反転してはいけない。要するに、次はbit二項演算の中から、0と行うと何も起きないが、1と行うとbitが反転するような操作を探さなければならない。

そしてそれは存在する。xorだ。片方が1だと(今回は負の数の場合)、もう片方が1なら0、0なら1とビットが反転する。片方が0だと(今回は正の数の場合)、もう片方が1なら1、0なら0と何も起きない。なのでこれが探していた演算だ。

という訳で以下のようになる。

    正の数    | 負の数
    seeeffff | seeeffff
    00101001 | 10101001
bor 00000000 | 11111111
------------ | --------
    00101001 | 11111111
xor 00000000 | 11111111
------------ | --------
    00101001 | 00000000

ReLUができたではないか。ここまで来ると、notしてandでもいいということに気づくが、まあそれは何でもいい。実装は以下のようになる。reinterpret_cast が長いので、floatstd::int32_t を変関する ftoiitof を実装したことにする。それはreinterpret_castでもできるし、 union を使って読み替えても良い。

float ReLU(float x)
{
    const auto y =  ftoi(x) >> 31;
    return itof((ftoi(x) | y) ^ y);
}

通常の ReLU はこうだ。

float ReLU_normal(float x)
{
    return (x > 0.0) ? x : 0.0;
}

ではどうなるか比べてみよう。コンパイルしたのちobjdumpで逆アセンブルする。

ReLU:
    movd %xmm0, %eax
    movd %xmm0, %edx
    sarl $31, %eax
    notl %eax
    andl %edx, %eax
    movd %eax, %xmm0
    retq

ReLU_normal:
    maxss    (%rip), %xmm0
    retq

はい。max って1命令でしたね! これは実際に逆アセンブルした時に気づいた。そして書いている間は ReLUmax(x, 0.0) であることも頭になかった。

確かに今回書いたコードには分岐はない。だが、それ以上に命令が多い。知っている限りmaxのレイテンシは1か2なので、シフトやビット命令をしているこちらの方が時間がかかってしまうだろう。

物事に熱中している間は根本的なところに気づかない、という良い例だった。皆さんもたまには立ち止まって出発地点を確かめましょう。

tr1とboost/tr1でis_permutationがつらい話

なんとも重箱の隅をつつくような話だ。

私が開発に参加しているプロジェクトがあり、そこでは(古くから開発されているので)C++98が使われている。だが先進的なものも積極的に使おうとするプロジェクトでもあるので、Boostやtr1を使ってもいた。CMakeでtr1があるかどうかを確認し、存在していたらそちらをusingもしくはtypedefするという方針である。例えば、

#ifdef HAVE_CXX11_ARRAY
#include <array>
namespace hoge
{
template<typename T, std::size_t N>
struct array_getter{typedef std::array<T, N> type;};
}
#else
#include <boost/array.hpp>
namespace hoge
{
template<typename T, std::size_t N>
struct array_getter{typedef boost::array<T, N> type;};
}
#endif

のような感じだ。C++98ではテンプレートエイリアスが存在しないため、構造体を使っている。まあ、この例ではstd::arrayboost::arrayは名前が同じなのでusing std::array;でもいいと思う。

ところで、ある日私はテストを書いており、std::is_permutationが使いたくなった。そのテストでは配列持っている値は保証できるが順番は保証できないたぐいのものだったので、正解配列のpermutationであればOKだろうという判断である。今思えばstd::setに内容をコピーして正解集合と比較すればよかったのだが、その時はstd::setの存在を完全に忘れていた。

で、最初にお話しした通りそのプロジェクトではC++98が使われている。よってC++11以降で追加されたstd::is_permutationは使えない。だがC++erの強い味方Boostは使っていいことになっているので、Boost.Algorithmboost::algorithm::is_permutationが使える。

はずだった。

手元で動くことを確認した後、Travisからfailedメールが来た。調べてみると、異なる2つのstd::tr1::tupleが定義されているようだ。しかし、何故だ?

主な変更箇所はis_permutationだったので、少し覗いてみることにした。TravisではBoost 1.54が使われていたので(!)、boost.orgからダウンロードし、boost/algorithm/cxx11/is_permutation.hppを開く。

#include <boost/tr1/tr1/tuple> // for tie

とある。どうやらstd::pairに対してtieを使いたかったようだ。そのためにboost/tr1/tr1/tupleをインクルードしている。しかし、ご存知の通りboost/tr1の実装とGCCtr1実装は異なるものである。さらに、boost/tr1の内容はstd::tr1名前空間に定義される。前述した通りそのプロジェクトはtr1の機能も使っている。よって衝突が起きる。この問題の行は1.56.0時点でなくなっている。

仕方がない、と思い、その時std::setのことを思い出せば良かったのだが、何故か「is_permutationくらい自分で実装するか」と思ってしまった。本来は値の同一判定をする関数オブジェクトなどを取れるようにしてかなりジェネリックに書かないといけないのだが、今回は単にテストに使うためだけのものなので、最も簡単なものでよかろうと思い、それらの機能は無視することにした。

そして実装した。テストコードは単一の.cppファイルなので、別段名前空間を分ける必要もなかろうと思い、以下のような関数を定義した。

template<typename Iterator1, typename Iterator2>
bool is_permutation(const Iterator1 first1, const Iterator1 last1,
                    const Iterator2 first2, const Iterator2 last2);

ところが今度は手元でコンパイルが通らなかった。オーバーロード解決がambiguousだというのだ。明らかに解決できるのは私が定義した関数だけだろう、と思ったのだが、数秒経って気付いた。手元で使っているGCCは7.3で、-std=c++98などを付けなければ自動的にC++14が選択される。そして-std=c++98は使っていなかった。なのでこれと同じ形をした関数定義がstd名前空間内に存在する。

さらに、C++にはArgument Dependent Lookup (ADL)がある。これは関数の引数が何かの名前空間(例えばstd)で定義されているものなら、関数の候補をその名前空間(例えばstd)から"も"探すというものだ。この利便性と危険性に関しては星の数ほど記事があるので適宜googleしていただきたい。

で、何が起きたかというと、このis_permutationに渡していたIteratorクラスは、どちらもstd::vector<T>::iteratorだった。これは当たり前だがstd名前空間で定義されている。なので、std名前空間にある関数が全てオーバーロード解決の候補になる。GCC7.3ではデフォルトC++14なので、引数がマッチするstd::is_permutationが存在する。ところで私が定義したis_permutation関数もマッチする。よってambiguousになる。

気付くかこんなもん!!!

解決策は簡単で、自作is_permutation関数を適当な名前空間で囲み、使うときに名前空間を指定して呼べばよい。

namespace hoge
{
template<typename Iterator1, typename Iterator2>
bool is_permutation(const Iterator1 first1, const Iterator1 last1,
                    const Iterator2 first2, const Iterator2 last2);
}

hoge::is_permutation(v1.begin(), v1.end(), v2.begin(), v2.end());

あるいは、もっと単純な解決策がある。is_permutationという名前にしないことだ。そうすれば標準ライブラリと名前が被って困ることはない。


ところで、何も関係ない話だが、この前留学生に「重箱の隅をつつくってどういう意味?」と尋ねられ、「重要でない小さなことばかり議論することだ」と答えた。重箱のことは知っていたようなので、感覚もわかってもらえたようだ。すると次に、「では、豆腐の角に頭をぶつけるとは?」と聞かれ、一体どこで知ったのか非常に気になったのだが、「それは罵倒語で、馬鹿にしつつgo to hellのようなことを言っているのだ」と答えた。彼がどこでこれらの言葉を知ったのかというと、どうやら「角」と「隅」の違いについて調べていたらしい。両方vertex周辺の領域を指すが、概ね、角は出っ張っているところを言い、隅は凹んでいるところを言う、ということについて調べていた時にわからないセンテンスが出てきたので聞いてみたらしい。

最後に、「でも、”豆腐の角に頭をぶつけて死んでしまえ”は冗談だと思われるかも知れないから、罵倒語としてはあまり有用ではない。もっと端的に表現するべきだ」とも伝えておいた。これで、日本人と喧嘩になった時も、彼の怒りが正しく伝わるだろう。

一番簡単な画像フォーマット

世の中には画像フォーマットが沢山ある。データを圧縮してサイズが小さくなるフォーマットや、様々なメタデータを持てるフォーマットなど色々だが、自分で作るとなると何が一番楽だろうか。 ある意味ではsvgだろうが、今回はビットマップということにする。svgでrectを置きまくる? いやそういう趣旨ではない。

なんだろうかとは言ったが、これには確実な答えがある。PNMだ。

PNM (画像フォーマット) - Wikipedia

このフォーマットは冗談抜きに中身をエディタで表示しても画像が見える。上記リンクのWikipediaから引用すると、一番単純な白黒画像(PBM)は以下のようになる。

P1
# This is an example bitmap of the letter "J"
6 10
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
1 0 0 0 1 0
0 1 1 1 0 0
0 0 0 0 0 0
0 0 0 0 0 0

カラーのPPM形式は以下だ。 アスキー形式だとコメントを入れられるところも人間が扱うことを考慮している雰囲気がする。

P3
# The P3 means colors are in ASCII, then 3 columns and 2 rows, then 255 for max color, then RGB triplets
3 2
255
255 0 0
0 255 0
0 0 255
255 255 0
255 255 255
0 0 0

というわけでこの説明を書き始めたのだが、たらたら思いつくままに書いているとわかりにくい文章になったので系統立てて書くことにする。 まず、PNMフォーマットというのは総称で、実際には大きく分けて3つの形式があり、それぞれが二値画像、グレースケール、RGBカラーに対応している。 さらに、それらのそれぞれがアスキーとバイナリの2つのフォーマットで保存することができる。持っている情報は同じだ。

二値画像、PBM

最初に紹介した形式だ。ファイルの最初はP1\nで始まる必要がある。その後画像のサイズが横 縦の順で書かれ、その後0または1が続く。 注意すべきことは、ビットが立っているところが黒で、それ以外が白ということだ。通常255が一番明るい色なのでこれは少し混乱を生むかも知れない。

P1
# This is an example bitmap of the letter "J"
6 10
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
0 0 0 0 1 0
1 0 0 0 1 0
0 1 1 1 0 0
0 0 0 0 0 0
0 0 0 0 0 0

ところで、これは全てのPNMアスキー形式に共通することだが、改行はフォーマット識別子以外のどこで行われても許される。これはWikipediaを読んでも書いていなかったので適当なPNM画像を作って改行を入れながらビューワで見ることにした。大抵どこに入れても見ることができた。規格を参照していないのでそうあるべきかはわからないが、そうなっている方が便利なのは間違いない。このせいでファイル読み込み関数が少し面倒になってしまうのだが。

PBMのバイナリ形式はこれもまた厄介で、ビットごとにピクセルが対応している。だが、画像の一列ごとにパディングが入り、LSBは放置される。要するに上の画像をバイナリにすると以下のようになるだろうということだ。

50 31 0a 36 20 31 30 0a 08 08 08 08 08 08 88 70 00 00
P  1  \n  6     1  0 \n 以下参照
0 0 0 0 1 0 0 0 -> 0x08
0 0 0 0 1 0 0 0 -> 0x08
0 0 0 0 1 0 0 0 -> 0x08
0 0 0 0 1 0 0 0 -> 0x08
0 0 0 0 1 0 0 0 -> 0x08
0 0 0 0 1 0 0 0 -> 0x08
1 0 0 0 1 0 0 0 -> 0x88
0 1 1 1 0 0 0 0 -> 0x70
0 0 0 0 0 0 0 0 -> 0x00
0 0 0 0 0 0 0 0 -> 0x00

それぞれの行の後ろに、8の倍数になるまで関係のないビットが追加される。一行の終わりでそのような処理がされるが、今回は行ごとのピクセルが6つしかないので、全てのバイトがパディング入りになる。

ところでご覧の通り、バイナリ形式であっても画像のサイズはASCIIで保存されている。これはおそらく、整数値をバイナリで保存するときにそのサイズや符号の有無を定義するのが難しいとかそういう理由だろう。ちなみにコメントもバイナリ形式に含めることができて(今知った)、バイナリであっても#に相当するASCIIコードから\nまでは飛ばされるらしい。

グレイスケール、PGM

グレイスケールも基本は同じだ。ただ、最大値を記しておける点が異なっている。この最大値と同じ値のとき、そのピクセルは白くなる。

Wikipediaから例を引用すると、

P2
# Shows the word "FEEP" (example from Netpbm man page on PGM)
24 7
15
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
0  3  3  3  3  0  0  7  7  7  7  0  0 11 11 11 11  0  0 15 15 15 15  0
0  3  0  0  0  0  0  7  0  0  0  0  0 11  0  0  0  0  0 15  0  0 15  0
0  3  3  3  0  0  0  7  7  7  0  0  0 11 11 11  0  0  0 15 15 15 15  0
0  3  0  0  0  0  0  7  0  0  0  0  0 11  0  0  0  0  0 15  0  0  0  0
0  3  0  0  0  0  0  7  7  7  7  0  0 11 11 11 11  0  0 15  0  0  0  0
0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0

まあ、これだけだ。今回はバイナリの時にパディングが入ることはなく、書くピクセルが1バイトで保存される。要するに255階調しか色調は存在しないのだ。一応、2バイトへの拡張は存在するが、エンディアン関係の問題があって微妙な扱いになっているように書かれている。

カラー、PPM

同じだ。Wikipediaの例ではピクセルごとに改行されているが、そうしないといけないというわけではない。

P3
# The P3 means colors are in ASCII, then 3 columns and 2 rows, then 255 for max color, then RGB triplets
3 2
255
255 0 0
0 255 0
0 0 255
255 255 0
255 255 255
0 0 0

pnm++

というわけでこれは簡単なので自分で画像を作るときはいっちょ使ってみるかと思ったのだがC++用ライブラリがさほど充実していないように見える。ちゃんと検索していないだけかもしれない。恐らくみんなその場でざーっと書いてしまっているのだろう。それでいいと思う。ただ、私もそれを一応したので、ここに設計含めて説明を載せておこうとも思う。

まず、こんなに単純なフォーマットなのだから、コードはそんなに長くならないだろうと思った。1000行行かないのではないかと(これは後で盛大に超えてしまったので正確な推測ではなかった)。なので単一ファイルのヘッダーオンリーライブラリにすることにした。これならインストールなどの問題は限りなくなくなる。コピーして置いてインクルードするだけだ。こんなに簡単なことはない。

次に、C++なのだから、速度は最低限は出て欲しい。なので、データの局所性のために画像データは1次元配列で持つことにした。それをラップして二次元配列のように振る舞わせる。at(size_t, size_t)は導入するとして、operator[]が一行分のrangeをラップしたプロキシクラスを返すことにする。

そして、それぞれの画像フォーマットから出てくるデータは、pixelクラスをtemplateとして受け取るクラスにする。writeはそれでオーバーロードして、readpixel型をtemplateとして受け取ることにしよう。

ということで作った。ライセンスはMIT、要求はC++11、STL以外の依存はなし。適当にマンデルブロ集合などを描いて遊んでいる。

GitHub - ToruNiina/pnm: pnm format(pbm, pgm, ppm) IO for modern C++ (single header only library)