apt-get upgradeでfishが大量のエラーを吐くようになった

ま た か (bobthefishでstring関係のエラーが出た - in neuro

軽い気持ちでapt-get update && apt-get upgradeしたところ、fishが2.7.0になった。その後、argparseがないとかなんとかのエラーが出る。 bobthefishが内部で使っているコマンドなのでテーマを描画しようとする度に大量のエラーが出て、コンソールが使い物にならない。

aptレポジトリは既にfishが用意しているものなので乗り換え先もない。仕方がないのでapt版は捨てて自前ビルドすることにした。 しばし懐かしのbashに戻ることになる。補完が効かなくなるので少しストレスフルだ。

purgeは設定ファイルも消した気がするので、config/fish/config.fishも消えたりしないよなと少し怖くなったのでremoveにする。

$ sudo apt-get remove fish

fish公式からtarballを持ってきて、解凍し、ビルドする。

$ wget https://fishshell.com/files/2.7.0/fish-2.7.0.tar.gz
$ tar xvf fish-2.7.0.tar.gz
$ cd fish-2.7.0
$ ./configure
$ make
$ sudo make install

これで/usr/local/にfishが入った。とりあえず起動してみる。これで直らないなら仕方ない、fishのgitレポジトリから最新版を取ってきて、それでもダメならissueを立てるしかない。

$ /usr/local/bin/fish
~>

よろしい。テーマも元通り表示され(設定がそのまま使えている可能性が高い)、エラーメッセージも出ていない。

とりあえずこのままfishを使い続けることとして、pathがaptの頃と変わってしまったので(/usr/bin/fish -> /usr/local/bin/fish)、いくつかの設定を変えておかないと死ぬ危険性がある。

まずはchshだ。

$ chsh -s /usr/local/bin/fish [username]
you may not change the shell for [username]

は? ちょっとよくわからないですね……

紆余曲折あり、/etc/shellsを変えていないことに気付いた。

$ sudo su
# vim /etc/shells
(/usr/local/bin/fishを書き足す)

これでchshは通った。

次はtmuxだ。実を言うと、そのままtmuxに入った後新規ウインドウを作れなかったので気付いた。 .tmux.confに以下のような部分があったので直す。

# default shell
set-option -g default-shell   /usr/local/bin/fish
set-option -g default-command /usr/local/bin/fish

なぜだかよくわからないが一度tmuxのセッションを全て閉じないと解決しなかった。

というわけでとりあえずfishは動くようになった。

std::hash<integral_type>

ふと、std::hash<std::size_t>の実装がどうなっているか気になった。

ハッシュ関数は、何かの型の値を整数型に変換する。特にstd::hashsize_tに変換する関数である。同じ値に対しては同じハッシュ値を返し、別の値同士が同じハッシュ値を取らないようにする。一般にはハッシュ値を計算すべき型の取りうる値よりもハッシュ値の型がとる状態の方が少ないことが多いので、異なる値のハッシュ値が同一になり得る。これを衝突と呼ぶ。

ハッシュ関数は長い文字列などを単一の数値型に落とし込むことができるので、重複の検出に用いることができる。同一の文字列は必ず同じハッシュ値を保つので、ハッシュ値を使ってアクセスした配列の要素に文字列を挿入していくと、重複した文字列は必ず同じ配列要素に含まれる。これによって「同じである可能性のある文字列」の集合をかなり減らすことができ、力ずくの探索に比べて効率よく重複した文字列を検出できる。しかしハッシュ関数が頻繁に衝突するようだと、「同じである可能性のある文字列」の集合はあまり小さくならず、効率もあまりよくならない。また、ハッシュ関数の計算に文字列そのものの比較の100万倍の時間がかかるようだと、これも文字列そのものを比較する方が高速だ。なので、ハッシュ関数は十分高速に動作し、かつハッシュ値を十分散らす必要がある。

同様の使い道として、値に対応するキーのハッシュ値を配列のインデックスとして使う「ハッシュテーブル」がある。これは任意の型をキーとして値を繋いでおける連想配列だが、二分探索木と異なり定数時間でアクセスが可能である。ハッシュ関数の計算量は連想配列のサイズに無関係で定数だし、インデックスが求まれば配列よろしく定数時間でアクセスできるからだ。ここでも衝突が問題となる。二つのキーが指す要素インデックスが衝突してしまうとキーと値の対応が保てなくなるので、何らかの方法でどちらかのキーを別の位置へ割り当てる手法が必要になる。もちろんこのような必要はない方が良い。この用途に使うためにもハッシュ関数は十分高速で、かつハッシュ値を十分散らさなければならない。

表題に戻ろう。異なる2つのsize_tの値が異なる2つのsize_tの値になるように変換するもっとも簡単で高速だろう方法は、恒等変換だ。ほかの組み込みの整数は大抵の場合size_tよりもサイズが小さい。なのでintegral_typeは全て、単純なsize_tへのキャストがハッシュ関数に求められる2つの条件を満たすだろう。これらのハッシュ関数はどう実装されているのだろうか。謎のビット演算によってsize_tの値がなす集合の各地へ散らされているのだろうか。それとも単にそのまま返されているのだろうか。

というわけで見てみることにした。ポピュラーだろうからGCC実装にしよう。bits/functional_hash.hppの中に定義がある。 gcc/functional_hash.h at master · gcc-mirror/gcc · GitHub 見ていくとポインタへの部分特殊化があった。引用してみる。

  template<typename _Tp>
    struct hash<_Tp*> : public __hash_base<size_t, _Tp*>
    {
      size_t
      operator()(_Tp* __p) const noexcept
      { return reinterpret_cast<size_t>(__p); }
    };

reinterpret_castしている。性質上、size_tのサイズとポインタ型のサイズは同じであることが期待される。なので値は変えずそのまま返している。他の整数型はどうだろう。そのすぐ下に、マクロ定義がある。これは「trivialなハッシュ」のための特殊化を作るマクロで、static_cast<size_t>だけして返すようなハッシュ関数を定義している。そして整数型のそれぞれに対するマクロ展開が延々と続く。使い終わったらちゃんとundefしてあるので安心だ。

#define _Cxx_hashtable_define_trivial_hash(_Tp)     \
  template<>                        \
    struct hash<_Tp> : public __hash_base<size_t, _Tp>  \
    {                                                   \
      size_t                                            \
      operator()(_Tp __val) const noexcept              \
      { return static_cast<size_t>(__val); }            \
    };

という訳で、恒等変換が先に述べたハッシュ関数に期待される条件を両方満たす時は「trivialな」ハッシュ関数となり、GCC実装の標準ライブラリでもそのように実装されていることがわかった。もしかしたら自作ハッシュ定義で少しだけ速度を挙げられるかも、と一瞬期待したので悔しい。

しかし考えて見てほしい、ハッシュ関数がほぼ自明になるケースが整数型以外にもある。8文字以下の(1byte)文字列やstd::pair<bool, int>のような、全部合わせて64bit以下の整数型N個の組だ。これらは単に連結してstd::size_tに変換すれば良い。少し面倒だが以下のようなことになるだろうか。

template<>
struct hash<std::pair<bool, std::int32_t>>
{
    std::size_t
    operator()(std::pair<bool, std::int32_t> p) const noexcept
    {
        return (static_cast<std::size_t>(p.first) << 32ul) |
               (static_cast<std::size_t>(p.second) & 0x00000000ffffffff));
    }
};

あるいはsizeof(T) == 8ならそのままreinterpret_castでも良い。何となく、アセンブラの方が「あのレジスタの下32bit部分にこれを書き込む」と言うような命令で高速化できそうな気もする。

今までstd::pairハッシュ値を計算するときはboost::hash_combine的なものを使っていたが、ビットシフトで散らしてから繋げるよりも単にこうやって繋げた方が早い可能性はある。となると今まで少し時間をロスしていたのだろうか。今まで「std::pair<T1, T2>とかstd::tuple<Ts...>に対する特殊化として標準でhash_combineで繋げたバージョンを用意してくれればいいのに」と思っていたが、そうなるとreinterpret_castによる高速化の機会を逸するのが提供されない理由だったのだろうか。

ところで、上のような「trivialな」ハッシュ関数が使えない場合が存在する。そのような場合の一つに、改竄の検出がある。大きなファイル全体のdiffを取るのではなく、ハッシュ値が一致しているかを確認するのだ。例として、広く使われているプログラムの配布元と使用者の間に割って入って、悪意のあるコードを実行するよう改変したプログラムにすり替える攻撃を考える。もし配布元がプログラムのハッシュ値を公開していれば、この攻撃は極端に難しくなる。衝突しないよう注意深く設計されたハッシュ関数の返す値を衝突させ、かつ悪意のあるコードを実行するように改変を加えるのは容易ではないからだ。このような用途に使うハッシュ関数が返すべき値は、衝突しにくいことはもちろん、予測不能であることが望ましい。当然このような用途に「trivialな」ハッシュ関数を使うのは愚の骨頂だろう。上記のような高速化は、重複検出、検索、ハッシュテーブルについて行われるべきものだ。

boost::enable_ifの居所について

C++11のstd::enable_ifが使えない環境だと、boost::enable_ifなしにはテンプレート関数をガンガン使っていくのは厳しい。しかし、boost::enable_ifは途中で定義されている場所が変わっている。 boost 1.65.1のドキュメントを見てみよう。 Boost Utility Library - 1.65.1 enable_ifはBoost.Coreに移したと書かれている。ついでにboost/utility/enable_if.hppにも、以下のように書かれている。

/*
 * Copyright (c) 2014 Glen Fernandes
 *
 * Distributed under the Boost Software License, Version 1.0. (See
 * accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_UTILITY_ENABLE_IF_HPP
#define BOOST_UTILITY_ENABLE_IF_HPP

// The header file at this path is deprecated;
// use boost/core/enable_if.hpp instead.

#include <boost/core/enable_if.hpp>

#endif  

ではboost/core/enable_if.hppを使おう、と思って書いた所、別の環境に行くとコンパイルに失敗した。古いBoostが入っていたのだ。 その時はどうせ新しめのBoostにしかない機能をいくつか使っていたので野良で入れたものの、できれば古いBoostでも動くようにWorkaroundを設けたい。

Workaround自体は非常に簡単に実装できる。

#if BOOST_VERSION >= WHEN_ENABLE_IF_MOVED_TO_CORE
#include <boost/core/enable_if.hpp>
#else
#include <boost/utility/enable_if.hpp>
#endif

問題は、どのバージョンで移動したのかぱっと見わからなかったことだ。リリースノートも少し見たが、見つけられなかった(探し方が足りないのかも知れない)。

仕方がないのでcommit logを検索して2014年8月頃の話だということを突き止めた後(boostorg/utility: 492fd7f091c73494fb0062de586adc792f97c14)、boost.1.55.0, 1.56.0, 1.57.0をダウンロードしてきて確認した。 すると、boost 1.55.0ではutility/enable_if.hppに実装が書かれているのに対し、boost 1.56.0ではdeprecatedになっている。 というわけで、Workaroundができた。

#if BOOST_VERSION >= 105600
#include <boost/core/enable_if.hpp>
#else
#include <boost/utility/enable_if.hpp>
#endif

謎の演算子

よく知られたネタだと思うが、while文中でカウントダウンするための演算子がある。

int N = 10;
while(N --> 0)
{
    std::cout << N << ' ';
}
9 8 7 6 5 4 3 2 1 0 

逆方向もあるが、カウントダウン演算子にもインクリメント・デクリメントの前置・後置と同様の違いがある。 左向きカウントダウン演算子は、値が等しくなる時にfalseになるので、この場合最後の0は出力されない。

int N = 10;
while(0 <-- N)
{
    std::cout << N << ' ';
}
9 8 7 6 5 4 3 2 1 

というのはウソで、while(N --> 0)は正しく書くとwhile((N--) > 0)だ。

続きを読む

動的メモリ確保なしで派生クラスを格納できるクラスを作った

久方ぶりにdomain-specificでないコードを書いた気がする。

Qiitaに基本的なアイデアだけ書いたので、こちらでは少し立ち入った話をする。

tl;dr

先に最大サイズを決めておくことで、そのサイズ以下のオブジェクトを格納できるクラスを作った。 格納できるクラスはあるBaseクラスから派生している必要がある。

github.com

続きを読む

constな変数の初期化とラムダ

この前、少し初期化が面倒な数値のリストを作らなければならなくなった。しかも結構急いでいた。

適当な例として以下のようなものを考える。

const std::vector<std::size_t> indices{1,4,7,10,13, ... 298}; //流石に手では書けない

こんなもの、for文を使えばいいのでは?

std::vector<std::size_t> indices;
for(std::size_t i=0; i<100; ++i)
    indices.push_back(i*3+1);

上のコードは完璧ではない(ex. reserveするべきだ)が、とりあえず動きはする。

しかしその後少し面倒なことをするので、できれば私はこれをconstにしたかった。不注意で書き換えてしまうのが怖かった。

外部でCSVを作って、

const std::vector<std::size_t> indices{
#include "indices.csv"
};

とするのも一瞬考えたが、流石に却下した。

例えばBoost Iteratorを使えば、これは普通にIteratorを2つ渡してその範囲を使って初期化するコンストラクタが使える。

const auto f = [](std::size_t i){return i*3+1;};
const std::vector<std::size_t> indices(
    boost::make_transform_iterator(
        boost::make_counting_iterator<std::size_t>(1), f),
    boost::make_transform_iterator(
        boost::make_counting_iterator<std::size_t>(100), f)
    );

だが長い。正しいやり方だが、その時私は本当に急いでいたので(実際、マクロ定義をして一部のコードを生成した程だ)、これはちょっと許容できなかった。

そして、関数の返却値としてならconstで受けられる、と思ったあと、一瞬関数を作ろうとして、すぐに一時的なその場で使える関数の存在を思い出した。

lambdaだ。

const std::vector<std::size_t> indices = [](){ // ラムダ定義開始
    std::vector<std::size_t> v;
    for(std::size_t i=0; i<100; ++i)
        v.push_back(i*3+1);
    return v;
}(); // lambda定義を終了し "}"、即座に呼び出す "()"。
// その場でラムダの中身が実行され、値が返る。それが`indices`になる。

その場でlambdaを作って、その中で必要なものを生成し、その場でconstなものに代入した。

今までstd::algorithmとの組み合わせとか、関数を渡すというような状況でばかり使ってきたが、lambdaにはこんな使い方もある。綺麗な書き方かどうかはおいておいて。

現代のC++

C++17での機能追加について、ネット上では「項目一覧見ただけで読む気失せる」「入門不可能」「闇」「誰も使いこなせない」「絶対に使いたくない」などと盛り上がっている。

だが私は一概にはC++はどんどん初心者を見捨てているとは言えないと思っている。C++は規格のアップデートの度に、その場しのぎだったり一貫性を欠いたりするルールを廃し(vector<vector<int> >の空白や、template template parameterにtypenameを許可など)、より意図が明快で細かいことを考えなくていい文法が追加され(型推論、構造化束縛、range-based forなど)、古式ゆかしい形式よりも安全で使いやすいライブラリ(optional、string_viewなど)が追加されてきた。たまにC++98を使うとよくわかるが、最新のC++はかなりユーザーに優しい。

つまりどちらかというとC++はアップデートの度にユーザーの方に寄ってきているのだ。ユーザーが欲していた機能を追加し、ユーザーの評判が悪かった機能を捨てて。捨てることになったものを中々本当には切り離さないので(トライグラフは誰が使っていたのだろう)外から見ていると肥大化しているように見えるが、使っている側としてはどんどん便利になってきていると感じている。むしろ、単に使うだけの静的スクリプト言語という用途にすらなり得ると思っている。インタプリタないけど(CLing使ったことなし)。

続きを読む