2011年11月12日土曜日

配列とテンプレート関数

g++のバージョンは4.5,vc++は2010.

C/C++で配列を引数にとるには,ポインタで受ける方法と参照で受ける方法の2つがある.
なので,次のコードは関数のオーバーロードがあいまいという理由でコンパイル時にエラーになる.

#include <iostream>

void f(int * p){
  std::cout << "pointer version" << std::endl;
}

void f(int (&a)[2]){
  std::cout << "array version" << std::endl;
}

int main(){
  int * p;
  f(p); // ポインタ版が呼ばれる.

  int a[2];
  f(a); // これがNG.オーバーロードがあいまい.
}

これをテンプレート関数にしてみるとどうだろう?
普通に考えると同じくエラーになりそうなものだけど,g++ではコンパイルOKになる.
理由はよくわからない.

template<class T>
void f(T * p){
 std::cout << "pointer version" << std::endl;
}

template<class T,std::size_t N>
void f(T (&a)[N]){
 std::cout << "array version" << std::endl;
}

/* main関数は略 */

さらに,1つ目の関数の引数からポインタ記号を外して,"void f(T p)~"とすると(汎用型の関数になってしまうけど),vc++でもコンパイルできる.
「T=非ポインタ型」で成立するテンプレート関数と「T=ポインタ型」で成立するテンプレート関数がある場合は前者が優先されるようだ.
ということで,g++でもvc++でも通用するコードを書くとするなら,boost.type_traitsを利用するのがよさそうだ.

#include <iostream>

#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_pointer.hpp>

template<class T>
typename boost::disable_if<boost::is_pointer<T>,void>::type f(const T & v){
  std::cout << "general version" << std::endl;
}

template<class T>
typename boost::enable_if<boost::is_pointer<T>,void>::type f(const T p){
  std::cout << "pointer version" << std::endl;
}

template<class T,std::size_t N> void f(const T (&a)[N]){
  std::cout << "array version" << std::endl;
}

int main(){
  int v;
  f(v); // 汎用版

  int * p;
  f(p); // ポインタ版

  int a[2];
  f(a); // 配列版
}

もちろん,部分特殊化+静的メンバ関数で同じことができるけど,こっちの方が読みやすい…ような気がする.

マクロでserialize関数を自動生成

シリアライズというのは,オブジェクトをバイト列に変換すること.その逆はデシリアライズ.
バイト列はファイルに保存したりネットワーク送信したりできるので,いろいろ便利に使える.
例えば,自作アプリの設定値をXMLフォーマットにシリアライズ/デシリアライズできるようにしておけば外部から簡単に設定値を変更できるし,定期的に最新のステータスをネットワーク送信してモニタリングしたりファイルに書き出してログを残したり,といった感じ.

C++でシリアライズをサポートする代表的なライブラリといえば,boost.serializationだと思う.
boost.serializationで自作クラスや構造体のシリアライズ手順を定義するのには複数のやり方が用意されているのだけど,普通は次のような書き方をする.

#include <iostream>
#include <vector>

#include <boost/cstdint.hpp>

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>

#include <boost/archive/xml_oarchive.hpp>

struct book{
  std::string title;
  std::vector<std::string> authors;
  boost::uint16_t year,pages;
// シリアライズ方法の定義:ここから
private:
  friend class boost::serialization::access;
  template<class Ar>
  void serialize(Ar & ar,const unsigned int ver){
    ar & BOOST_SERIALIZATION_NVP(title)
       & BOOST_SERIALIZATION_NVP(authors)
       & BOOST_SERIALIZATION_NVP(year)
       & BOOST_SERIALIZATION_NVP(pages);
  }
// ここまで
};

int main(int argc, char *argv[])
{
  book mcd;

  mcd.title = "Modern C++ Design";
  mcd.authors.push_back("Andrei Alexandrescu");
  mcd.year = 2001;
  mcd.pages = 337;

  // xmlフォーマットで標準出力
  boost::archive::xml_oarchive oa(std::cout);
  oa << boost::serialization::make_nvp("book",mcd);

  return 0;
}

シリアライズしたいクラスごとにこういうのを書いていけばいいのでお手軽といえばお手軽なのだけど,クラスを定義するたびにつらつらと書いているとやはり面倒に感じてくる.
ということで,次のようなマクロを用意しておくのはどうだろう?

// lazy_serialize.hpp

#define PP_VA_ARGS_SIZE(...)                         \
  PP_VA_ARGS_SIZE_I((__VA_ARGS__,25,                 \
                     24,23,22,21,20,19,18,17,        \
                     16,15,14,13,12,11,10,9,         \
                     8,7,6,5,4,3,2,1))

#define PP_VA_ARGS_SIZE_I(tuple) PP_VA_ARGS_SIZE_II tuple

#define PP_VA_ARGS_SIZE_II(_1,_2,_3,_4,_5,_6,_7,_8,             \
                           _9,_10,_11,_12,_13,_14,_15,_16,      \
                           _17,_18,_19,_20,_21,_22,_23,_24,     \
                           _25,size,...) size

#define PP_SERIALIZATION_NVP(z,count,array)                      \
  & BOOST_SERIALIZATION_NVP(BOOST_PP_ARRAY_ELEM(count,array))

#define LAZY_SERIALIZE(...)                               \
  private:                                                \
  friend class boost::serialization::access;              \
  template<class Ar>                                      \
  void serialize(Ar & ar,const unsigned int){             \
    ar LAZY_SERIALIZE_I((__VA_ARGS__));                   \
  }

#define LAZY_SERIALIZE_I(tuple)                              \
  LAZY_SERIALIZE_II(( PP_VA_ARGS_SIZE tuple,                 \
                      PP_SERIALIZATION_NVP,                  \
                      (PP_VA_ARGS_SIZE tuple,tuple) ))

#define LAZY_SERIALIZE_II(tuple) BOOST_PP_REPEAT tuple
#include "lazy_serialize.hpp"

/* 略 */

struct book{
  std::string title;
  std::vector<std::string> authors;
  boost::uint16_t year,pages;
  LAZY_SERIALIZE(title,authors,year,pages);
};

/* 略 */

紋切り型のシリアライズ定義が省略されて,シリアライズしたいメンバを列挙すればOKになる.
ただし,マクロ内に含まれるprivateタグが後続の属性に伝搬してしまうので,クラス定義の末尾に書くのがベター.

2011年11月10日木曜日

lubuntuでマウスの設定を変更

lubuntuは,ubuntuのデスクトップ環境をLXDEに差し替えた軽量のディストリビューション.
仮想マシンや低スペックの物理マシンでも比較的サクサク動作するので,もう3年くらい使っている.
ほとんど不満はないのだけど,ひとつ残念なのが,スタートメニューから起動できるマウスの設定用GUIフロントエンドを使っても設定が反映されないこと.
けっこう発覚しやすい不具合だと思うのだけど,最初のリリース(8.04)から執筆現在の最新版(11.10)まで,ずっと修正される様子がない.
ちなみにマウス設定をいじるには設定ファイルを直接書き換えればOK.

# sudo leafpad /etc/xdg/lxsession/Lubuntu/desktop.conf

HEWで仮想関数を含むコードをビルドするときの注意

HEWのバージョンは4.08,コンパイラはRenesasのSH用C/C++コンパイラでバージョンは9.04.

HEW(High-performance Embedded Workshop)といえば,ルネサス系マイコンの定番開発環境.…なのだけど,C++で仮想関数を使いたいときには一手間必要なのでメモ的エントリ.

適当にプロジェクトを作成して,仮想関数を含むコードを書いてビルドすると普通に成功するのだけど,このファームは仮想関数を呼び出すところで落ちてしまう.
なぜなら,HEWのデフォルト設定では仮想関数テーブルにアドレスが割り当てられていないから.
仮想関数テーブルというのは仮想関数の継承関係をまとめた一覧表みたいなもので,C++のバイナリで仮想関数を実行する場合には絶対に必要なもの.

ファームのメモリセクションに仮想関数テーブルを配置する方法は次のとおり.

「ビルド」→「XXXX Toolchain」→「最適化リンカ(タブ)」→「セクション(プルダウン)」→「編集」→"C"あたりをクリックして「追加」→"C$VTBL"を選択して「OK」→「↑」「↓」でそれっぽいところに配置.

ちなみに,仮想関数の呼び出しはテーブルを参照する手間があるので,通常の関数呼び出しよりも少しだけオーバーヘッドが大きい.組み込み系で仮想関数を使うかどうかは個人の判断でどうぞ.

g++とvc++の可変引数マクロの挙動の違い

g++は4.5,vc++は2010.

例えば可変引数マクロのとる引数の個数を数えたいと思ったとき,g++ならこう書けばOK.

#define NUM_ARGS(...) NUM_ARGS_I(__VA_ARGS__,8,7,6,5,4,3,2,1)

#define NUM_ARGS_I(_1,_2,_3,_4,_5,_6,_7,_8,n,...) n

int main(){
  // g++では4に展開される
  std::cout << NUM_ARGS(one,two,three,four) << std::endl; 
}


ところがこれ,vc++では意図通り動作せず,1が出力される.
どうもvc++では,NUM_ARGS_Iの引数が,

_1 = one,two,three,four
_2 = 8
_3 = 7
...
_8 = 2
n = 1

と展開されるようだ.これってC99の規格に沿ってるんだろうか?

vc++でもきちんと動かすには,一度タプル化するのがお手軽っぽい.
実際にboost.preprocessorでもこの手が使われている.

#define NUM_ARGS(...) NUM_ARGS_I( (__VA_ARGS__,8,7,6,5,4,3,2,1) )

#define NUM_ARGS_I(tuple) NUM_ARGS_II tuple

#define NUM_ARGS_I(_1,_2,_3,_4,_5,_6,_7._8,n,...) n