Rokiのチラ裏

学生による学習のログ

パイプラインset_segregate

※ 2018/3/11 ライブラリ整理より当機能は削除した。
set_segregateをパイプライン記法で。まあラップしただけ。

以下のように使う。

std::vector<int> 
        left = boost::irange(0,9) | srook::adaptors::copied,
        right = boost::irange(7,19) | srook::adaptors::copied;
std::vector<int> left_only,both,right_only;

// pairかtupleで渡す。set_segregateの引数にはコンテナ直でも開始位置を示すイテレータでも良い。
std::make_pair(left,right) | srook::pipealgo::set_segregate(std::back_inserter(left_only),std::back_inserter(both),std::back_inserter(right_only));
std::make_tuple(left,right) | srook::pipealgo::set_segregate(left_only,both,right_only);

left_only | srook::adaptors::print();  // 0 1 2 3 4 5 6
both | srook::adaptors::print(); // 7 8
right_only | srook::adaptors::print(); // 9 10 11 12 13 14 15 16 17 18

これはイテレータアダプタを作ると尚良いかもしれない。

iterator requirementsにassignableを要求される事による問題

※追記
当エントリは「STLコンテナの気まぐれな実装によってiteratorをassignされる事による問題」というタイトルであったが、これは気まぐれな実装ではなく、標準の文面に規定されたルールの範囲内での話であったため、誤った解釈に基づくようなタイトルや文面が編集されている。

標準が定めるイテレータの要件としてcopy assignableである事が明記されている。 *1

A type X satisfies the Iterator requirements if: X satisfies the CopyConstructible, CopyAssignable, and Destructible requirements (17.5.3.1) and lvalues of type X are swappable (17.5.3.2), and ...

STLコンテナには、アルゴリズムらの計算量を除いた実装規定というものが特にない。よって、本エントリの題名のように気まぐれにiteratorをassignしたところで何の違反性もないのだが、これについては規定した方が良いんじゃないかという意見。上記の通りcopy assignableでなければならないと定められている。

例えば、大抵のSTLコンテナはbegin/endのイテレータで初期化する事が可能である。

std::vector<int> v1(10,42);
std::vector<int> v2(v1.begin(),v1.end());

そして、かの有名なライブラリ、Boost C++ Librariesにはboost range adaptorsというイテレータアダプタを利用した効率的な範囲横断を促す事のできるライブラリが存在する。中でも、例えばboost::adaptors::filteredはクロージャを渡す事によってその条件に応じた範囲横断を行えるイテレータアダプタである。

std::vector<int> v(10);
boost::iota(v,0);
    
boost::range::copy(v | boost::adaptors::filtered([](const typename decltype(v)::value_type& x){return x%2==0;}),std::ostream_iterator<int>(std::cout," "));

では、このfilteredイテレータアダプタを用いて、範囲を初期化する。イテレータアダプタから直接初期化するためにはOvenToBoostなどのas_containerが、Boostと互換性があり良い。

std::vector<int> v1(10);
boost::iota(v1,0);
    
std::vector<int> v2 = v1 | boost::adaptors::filtered([](const typename decltype(v1)::value_type& x){return x%2==0;}) | boost::as_container; // v2は偶数のみで初期化される。

このコードは、GCC、Clangともに正しくコンパイルに成功する。ここで例えば、コンテナをstd::dequeにするとどうなるだろうか。

std::deque<int> v1(10);
boost::iota(v1,0);
    
std::deque<int> v2 = v1 | boost::adaptors::filtered([](const typename decltype(v1)::value_type& x){return x%2==0;}) | boost::as_container; 

clang(3.5〜5.0.0)のライブラリ実装上では正しくコンパイルに成功する。しかし、このコードはGCC(7.0.1までの全てのバージョン)でコンパイルに失敗する。エラー文は、この場合BoostをインクルードしているのでBoostライブラリでのassign失敗の旨が出力されるが、std::dequeの実装を実際に見て見ると、直接的にassign操作を行なっているコードが目につく(gcc7.0.1であれば、/c++/7.0.1/bits/deque.tcc:458:11)。これは、predに、ラムダ式が使えない事を意味する。boost::adaptors::filteredはfiltered_iteratorというイテレータアダプタを生成しその内部に指定されたクロージャオブジェクトを保持するラムダ式のoperator=は規格によってdelete指定されているため、イテレータをassignした段階で失敗する。

ラムダ式でないクロージャであればコンパイルに成功するが、ベンダによって異なる内部実装を気にしてpredをラムダ式で書かないというプログラマ側の負担は無駄であるように思う

struct functor{
    template<class T>
    bool operator()(const T& t){return t%2==0;}
};

std::deque<int> v1(10);
boost::iota(v1,0);
std::deque<int> v2 = v1 | boost::adaptors::filtered(functor()) | boost::as_container; // v2は偶数のみで初期化される。

GCCの実装では、何故イテレータをassignする必要があったのか、そこまではコードを追っていないので分からないが、このような仕様は少なくともイテレータアダプタによる初期化を妨げる要因となる。よってSTLコンテナのbegin/endコンストラクタはassignableを強要すべきではないと私は考える

一つ、妥協策ではあるが、解決策はある。内包するpredをラッパーで包み、無理やりassignableに見せかける事でコンパイルに成功する。

自作ライブラリ、srook range adaptor filterd_iteratorなどでは内部に当ラッパーを用いたクロージャを保持しており、gccで無事コンパイルを通している。

#include<srook/range/adaptor/copied.hpp>
#include<srook/range/adaptor/filterd.hpp>
#include<srook/range/adaptor/print.hpp>
#include<deque>
#include<boost/range/irange.hpp>

struct functor{
    template<class T>
    bool operator()(const T& t){return t%2==0;}
};

int main()
{
    std::deque<int> deq1 = boost::irange(1,9) | srook::adaptors::copied;
    std::deque<int> deq2 = deq1 | srook::adaptors::filterd([](const typename decltype(deq2)::value_type& x){return x%2==0;}) | srook::adaptors::copied;
    std::deque<int> deq3 = deq1 | srook::adaptors::filterd(functor()) | srook::adaptors::copied;
    deq2 | srook::adaptors::print();
    deq3 | srook::adaptors::print();
}

しかし、依然としてラッパーを噛ませている分オーバーヘッドが発生するので、あまり心地よくはない。

縦横比率を維持しつつ、横幅に合わせて画像を囲うボックスのheightを可変的に取る

仕事(アルバイト)でWebページを弄っている。widthとの比率が考慮されたheightを可変的に取らなければならない問題があり、cssだけでなんとかできないものかと考えていたが、思いつかなかったのでjqueryを使った話。

画像の横幅を囲うボックスのwidthを100%にし、その実際のサイズ値をjqueryで取得、元の画像の縦横比率が合うように横幅からその比率分を除算して縦幅をcssに書き込んでいる。

何かもっとスマートな方法はないだろうか。

brainf**k インタプリタジェネレータ

何だか凄く早い時間に起きてしまい、特にやることもないのと、最近アニメのセリフをbrainfuckの命令語に置き換える遊びが再発しているので、ジェネレータというかライブラリとも言えぬ小さいヘッダを書く事にした。

こう使う。一応元ネタさんと同じ命令語で動くようにしてある。

// kemono.cpp
#include<srook/brainfk/brainfk.hpp>

template<class... T>
constexpr auto make_array(T&&... ts) -> std::array<std::decay_t<srook::First_t<T...>>,sizeof...(ts)>
{
    return std::array<srook::First_t<T...>,sizeof...(ts)>{{std::forward<T>(ts)...}};
}

int main(const int argc,const char* argv[])
{
    using namespace std::string_literals;
    if(argc<2)return -1;

    const auto gengo_friends_tanoshi=
        make_array(
                std::wstring(L"たのしー!"),std::wstring(L"すごーい!"),std::wstring(L"たーのしー!"),
                std::wstring(L"すっごーい!"),std::wstring(L"なにこれなにこれ!"),std::wstring(L"おもしろーい!"),
                std::wstring(L"うわー!"),std::wstring(L"わーい!")
        );
    srook::brainfk_syntax<std::wstring,unsigned char> bksy(gengo_friends_tanoshi);

    const auto& status=bksy.file_open(argv[1]);
    if(status==boost::none)return -1;

    bksy.analyze();
    bksy.exec();
}

因みに世界に挨拶を捧げるプログラミング言語フレンズKemonoのコードはこちら。

たのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!うわー!すごーい!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たのしー!すっごーい!わーい!すごーい!なにこれなにこれ!たのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!うわー!すごーい!たーのしー!たーのしー!たーのしー!たーのしー!たのしー!すっごーい!わーい!すごーい!たーのしー!なにこれなにこれ!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!なにこれなにこれ!なにこれなにこれ!たーのしー!たーのしー!たーのしー!なにこれなにこれ!うわー!すっごーい!わーい!たのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!うわー!すごーい!たーのしー!たーのしー!たーのしー!たーのしー!たのしー!すっごーい!わーい!すごーい!なにこれなにこれ!たのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!うわー!すごーい!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たのしー!すっごーい!わーい!すごーい!なにこれなにこれ!たのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!うわー!すごーい!たーのしー!たーのしー!たーのしー!たのしー!すっごーい!わーい!すごーい!なにこれなにこれ!たーのしー!たーのしー!たーのしー!なにこれなにこれ!すっごーい!すっごーい!すっごーい!すっごーい!すっごーい!すっごーい!なにこれなにこれ!すっごーい!すっごーい!すっごーい!すっごーい!すっごーい!すっごーい!すっごーい!すっごーい!なにこれなにこれ!うわー!すっごーい!わーい!たのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!うわー!すごーい!たーのしー!たーのしー!たーのしー!たーのしー!たのしー!すっごーい!わーい!すごーい!たーのしー!なにこれなにこれ!うわー!すっごーい!わーい!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!なにこれなにこれ!

普通に以下のように実行。

$ clang++ -std=c++1z -Wall -Wextra -pedantic kemono.cpp
$ ./a.out hello_world.kemono

私は最近ポンコツエストというアニメが少し面白いと思っているので、その作中で出てくる言葉を命令語にしたく思う。

#include<srook/brainfk/brainfk.hpp>

//...

const auto ponkue=
    make_array(
            std::wstring(L"ブラック企業かよ"),std::wstring(L"アイスうめー"),std::wstring(L"ヒョウド!"),
            std::wstring(L"ドルファヘキセンダー"),std::wstring(L"おもらひ"),std::wstring(L"おねひ"),
            std::wstring(L"労災はおりない"),std::wstring(L"ハンマー")
    );
srook::brainfk_syntax<std::wstring,unsigned char> bksy(ponkue);

const auto& status=bksy.file_open(argv[1]);
if(status==boost::none)return -1;

bksy.analyze();
bksy.exec();

まあ、置き換えているだけである。この場合、世界に挨拶するためには以下のようなコードとなる。

ブラック企業かよヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!労災はおりないアイスうめーヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ブラック企業かよドルファヘキセンダーハンマーアイスうめーおもらひブラック企業かよヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!労災はおりないアイスうめーヒョウド!ヒョウド!ヒョウド!ヒョウド!ブラック企業かよドルファヘキセンダーハンマーアイスうめーヒョウド!おもらひヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!おもらひおもらひヒョウド!ヒョウド!ヒョウド!おもらひ労災はおりないドルファヘキセンダーハンマーブラック企業かよヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!労災はおりないアイスうめーヒョウド!ヒョウド!ヒョウド!ヒョウド!ブラック企業かよドルファヘキセンダーハンマーアイスうめーおもらひブラック企業かよヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!労災はおりないアイスうめーヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ブラック企業かよドルファヘキセンダーハンマーアイスうめーおもらひブラック企業かよヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!労災はおりないアイスうめーヒョウド!ヒョウド!ヒョウド!ブラック企業かよドルファヘキセンダーハンマーアイスうめーおもらひヒョウド!ヒョウド!ヒョウド!おもらひドルファヘキセンダードルファヘキセンダードルファヘキセンダードルファヘキセンダードルファヘキセンダードルファヘキセンダーおもらひドルファヘキセンダードルファヘキセンダードルファヘキセンダードルファヘキセンダードルファヘキセンダードルファヘキセンダードルファヘキセンダードルファヘキセンダーおもらひ労災はおりないドルファヘキセンダーハンマーブラック企業かよヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!労災はおりないアイスうめーヒョウド!ヒョウド!ヒョウド!ヒョウド!ブラック企業かよドルファヘキセンダーハンマーアイスうめーヒョウド!おもらひ労災はおりないドルファヘキセンダーハンマーヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!ヒョウド!おもらひ
./a.out hello_world.ponkue
Hello World!

※追記

上記のコードでもそのままコンパイルが通るが、Boost.parameter を使うヘルパーを噛ませておいたので、以下のように何のキーワードを何に設定するのかより明確に書ける。

あと見た目にインパクトがあるので、これらそれぞれの fizzbuzz のコード(?) を載せておく。

自宅ローカルサーバー(apache,motion,ping,AP)構築

前書き

Amazon dash buttonなどと連携させたmotionサーバー(監視カメラ)を以前構築したわけだが、最近は更に用途が増えてきている。

例えば、最近何故だかマンション内の回線が不安定であるのでpingを飛ばしたりしている。マンション内と断定したのは、定期的にプロバイダからの接続が切れる事を観測した事に帰結するのだが、まあ正直なところ原因ははっきりと定かではない。そこで、取り敢えず原因を探るためにも、不安定になるたびにpingを送信して、どの段階でノードがlostされたのか調べたりする必要がある。

また、別の用途としては、アクセスポイントとしての役割がある。GigaBitのルーターが欲しかったのだが、それに対応した無線LANルーターは割とお値段が張るので、学生身分も考えて有線LANルータにして、元から稼働している自宅サーバーをアクセスポイントにしてしまう事にした。

そして、それら全てを直感的に操作したり、情報にアクセスできたりする必要がある。まあ私だけが出来れば良いというのであれば必要ないのだが、彼女だけが自宅にいる時に使えないのも不便なので必要である。そのためには、apacheを使ってウェブサーバーを作り、そのページからグラフィカルなインタフェースで操作できるようにする事とした。

そんなわけで、以下に上記の機能を稼働させるサーバーを構築する作業をログを残す。


導入する機能まとめ

さて、導入する機能は以下の通りである。

  • LAMP
  • motion
  • cronでpingを飛ばしそのログファイルをphpで吸い上げる
  • AP化


環境は以下の通りである。

$ uname -a
Linux Vostro-Ubuntu 4.4.0-59-generic #80-Ubuntu SMP Fri Jan 6 17:47:47 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

MySQLは特に必要としないので厳密にはLAPといったところか。

apache,php導入

$ sudo apt-get install apache2 php libapache2-mod-php

apacheについては、諸々の設定が必要であるが、本エントリでは、取り合えず機能として目立つ設定内容だけを示す。*1 設定内容としては、まずapacheサーバーへのアクセスはローカルネットワーク内のマシンに限定する。また、すぐcgiを動かしたくなるとも思うのでそのように設定した。

<Directory /var/www/html>
     Options ExecCGI
     AddType text/html cgi
 
     AllowOverride All
     order deny,allow
     deny from all
     allow from 192.168.
     allow from 10.42.0.
</Directory>

このapacheサーバーは、apacheだけでなく、アクセスポイントともなっているため、そのアクセスポイントを使っているユーザー(10.42.0.)もallowする必要がある。

motion導入

motionについては、以前アップロードしたエントリを参照。 roki.hateblo.jp

cronでpingを飛ばしphpで吸い上げ

cronで定期的にpingを飛ばす。ログファイルを上書き出力させ、そのファイルをphpで読み込み、前述したページに表示させる。取り敢えず、pingは30分おきにexample.comへ送出する。

*/30 *    * * *   root    /bin/bash /var/www/script/check_ping.sh

shスクリプトは以下のような内容。

#!/bin/bash
# checkinfo.sh

cd /var/www/script
date > ../logfiles/ping.log
echo >> ../logfiles/ping.log
ping -c 10 www.example.com >> ../logfiles/ping.log
echo >> ../logfiles/ping.log
traceroute www.example.com >> ../logfiles/ping.log

そしてphpで吸い上げる。

<?php
    function file_print($filename,$filetype_name){
        $fp=fopen($filename,'r');
        print('<u><strong>'.$filetype_name.'</strong></u>:<br>');
        if($fp){
            if(flock($fp,LOCK_SH)){
                while(!feof($fp)){
                    $buffer=fgets($fp);
                    print($buffer.'<br>');
                }
                flock($fp,LOCK_UN);
            }else{
                print('File lock was failed.');
            }
        }
        fclose($fp);
        print('<br><br>');
    }

    file_print('../script/checkinfo.sh','script');
    file_print('../logfiles/ping.log','result');
?>

これで、定期的に実行されるpingを、apacheに設定されているhtmlページから参照できるようになる。

AP化

AP化は公開されているユーティリティツールを用いれば比較的容易に構築できる。

sudo apt -y install hostap
git clone https://github.com/oblique/create_ap
cd create_ap
sudo make install
create_ap --ieee80211n wireless_adaptor_name eth_adaptor_name SSID key &

以上で完了した。

ちなみに、ローカルネットワーク上のページは以下のような感じにしてみた。普通にhtmlとcssで書いた。

無駄に装飾を凝ってしまった気がするが画面中央の黄色い画像部分はmotionプロセスが起動すると自動的に監視カメラの映像に切り替わるようになっている。

*1:他の標準的なディストリビューションではhttpd.confによる設定が一般的だが、Ubuntuのaptでapache2を導入した場合は、/etc/apache2/apache2.confに設定内容が書かれるため、本エントリでもその内容をそのまま記載している。

あらゆるSTLコンテナをあらゆるSTLコンテナへ一発変換する(連想配列などを除く)

range adaptorライブラリを書いていて、copiedがあるのだから、集合の種類を指定できても良いだろうと思い、実装する事にした。

以下のように使える。

auto v = boost::irange(0x41,0x47) | srook::adaptors::to_range<std::vector>(); // boost::irangeと併用可能
auto li = v | srook::adaptors::to_range<std::list>(); // リストへ
auto t1 = v | srook::adaptors::to_range<std::tuple,6>(); // タプルへ
auto str = v | srook::adaptors::to_range<std::basic_string>(); // std::stringへ
auto ar = v | srook::adaptors::to_array<std::array,6>(); // std::arrayへ
auto str1 = v | srook::adaptors::to_string<std::wstring>(); // std::wstringへ
auto str2 = v | srook::adaptors::to_string<std::u16string>(); // std::u16stringへ
auto str3 = v | srook::adaptors::to_string<std::u32string>(); // std::u32stringへ

tupleに対する特殊化がされており、再起によって元集合のデータを取り出すようにしている。このコードのみではlistとarrayに対応できていない。まあ直ぐ書けるのだが、余談を挟むと、色々とこれ以外にも作業をしていたら、執筆段階で朝の4時となってしまったので一旦休眠し、後日書くことにした。

上記の通り対応した。arrayのテンプレートパラメータを特殊化させる事は不可能なため、別途設けているが何か良い方法はないだろうか。それと、to_rangeにbasic_stringを渡して文字列を生成する事も上記の通り可能ではあるが、chatTを指定する術がないため、必ずstd::stringを返すようにしている。よってこの書き方だとwstirng,u16string,u32stringに対応できない。そのために、一応、to_stringも設ける事にした。using namesapce std;using namespace srook::adaptors;を行うとstd::to_stringと名前が被りそうな雰囲気が漂うが、オーバーロード解決により正しく呼び出される。

C++14までの標準規格実装に準ずる厳密なhas_iterator

void_t detection idiomのエントリなどから何度か当ブログで登場している、イテレータを持つtypeであるかを判別するメタ関数has_iteratorであるが、これまでのエントリでは以下のような実装を示してきた。

template<class,class=std::void_t<>>
constexpr bool has_iterator_v=std::false_type::value;

template<class T>
constexpr bool has_iterator_v<T,std::void_t<typename T::iterator>> = std::true_type::value;

しかし、このメタ関数はiteratorクラスを継承したイテレータを持たないクラスに対して、誤った判定結果を返す。下記のコードではstatic_assertがfireして欲しいところだが、残念ながらコンパイルが通ってしまう。

static_assert(has_iterator_v<std::back_insert_iterator<std::vector<int>>>); // passing... ?!

std::back_insert_iteratoriteratorクラスを継承している。back_insert_iterator自体はiteratorであるが、iteratorを持っているわけではないので、この判定結果は間違いである。この問題を回避するためには、iteratorというエイリアスが見えているのか、それともiteratorという生のtypeが見えているのか(この場合、継承関係を調査する)を判定すれば良い。(コンパイラごとに分岐するマクロが記述されている理由は当エントリの追記に書かれている。)

表題として、C++14までのとしているのは、C++1zではiteratorクラスがdeprecatedとされる予定*1だからだ。しかし新たにC++1z制定以降に世間で書かれるコードがiteratorクラスを使わないにしても、今まで使われてきた標準ライブラリがどのように実装されるのか(そのままの可能性も大いに考えられる)は不明であるので、依然として対策は必要である。

追記

当エントリの内容は、比較的有名なコンパイラー(ClangやGCC)のバージョンごとで誤った解釈がされる可能性がある。

実際に処理内容として正しいコードは上記にあるものだが、コンパイラのバージョンによっては対応しなければならないかもしれない。 原因は今の所不明であるが、ライブラリ実装の問題ではなくコンパイラの解釈自体に問題があるのではないかと私は踏んでいる。