久しぶりに手持ちのライブラリを整理すると,嵌ってしまうネタが続々と登場です。今日のネタはこんなの。
// こんな構造体テンプレートを定義
template< typename T, int N >
struct B { };
// '+' 演算子を非メンバ関数でオーバーロード
template< typename T, int N >
void operator + ( const B<T,N>& a, const B<T,N>& b ) {
printf(" N is %d", N ); // N を表示
}
int main() {
B< int, 0 > b;
b + b; // '+' 演算子を使う
return 0;
}
VC++5.0 でいざコンパイル。
error C2783: (前略) 'N' 用のテンプレートの引数を減少できませんでした。 error C2676: 二項演算子 '+' (中略) は、この演算子または定義済の演算子に適切な型への変換の定義を行いません。
んまぁ,こんな感じでコンパイルできません。g++ ならコンパイルできるので,ボク悪くないです。
少しだけ良さげな回避策はこんなの。
// こんな構造体テンプレートを定義
template< typename T, int N >
struct B {
// 構造体内でメンバ関数として定義
void operator + ( const B<T, N>& arg ) { }
};
はー,よかった・・・。しかし次の問題はどう回避すればよいのやら?
// 左辺が int 型の場合
// こればっかりは非メンバ関数じゃないと・・・
template< typename T, int N >
void operator + ( int a, const B<T,N>& b ) {}
テンプレートの引数が int N のように値を要求するものではなく,typename N のように型を要求するものならば問題ないようです。
// こんな構造体テンプレートを定義
template< typename T, typename N >
struct B { };
// '+' 演算子を非メンバ関数でオーバーロード
template< typename T, typename N >
void operator + ( const B<T,N>& a, const B<T,N>& b ) {}
template< typename T, typename N >
void operator + ( int a, const B<T,N>& b ) {}
しかしこれは構造体の設計を大きく変えてしまうのであんまし意味なくない? そこで頭をひねって,こんな回避策にたどり着きました。
// これがトリック
template< int N >
struct TypedValue { enum{ Value = N }; };
// こんな構造体テンプレート
template< typename T, typename N >
struct B { };
// '+' 演算子
template< typename T, typename N >
void operator + ( int a, const B<T,N>& b ) {
printf(" N is %d", N::Value ); // こんな感じで N を使う!
}
int main() {
B< int, TypedValue<0> > b;
b + b; // '+' 演算子を使う
return 0;
}
"enum ハック" みたいなもんですか。このトリックを使う前はテンプレートに入力された N がそのまま値として使えましたが,このトリックでは N::Value を使います。まあ,当分の間はこのトリックでも困らないと・・思うですよ・・。
去年の今あたりからこの雑記を書き始めて,明日で一周年記念。たびたびコピーコンストラクタや代入演算子の挙動の罠に嵌ってはその度に雑記のタネにしてきたわけですが,今日もまた。この 365 日間で何をやって来たんだ自分。
今日のはこんなコード。
#include <stdio.h>
class AA {
public:
AA () {}
AA ( const AA& a ) { puts("copy ctor"); }
AA& operator = ( const AA& a )
{ puts("copy otor"); return *this; }
};
AA func()
{ return AA(); }
int main() {
AA a = func();
return 0;
}
関数 func() はクラス AA のインスタンスを生成します。クラス AA はコピーコンストラクタか代入演算子が呼び出されれば,何かを出力します。関数 func() で生成されたインスタンスが外部に伝わる仕組みはどうなってるんだろう? 我が愛コンパイラ VC++5.0 でコンパイルし,その実行結果:
E:\C\>test
E:\C\>
何も表示されませんでした。すなわち,コピーコンストラクタも代入演算子も呼び出されていません。もう少し詳細に調べてみると,なんとこのコードでクラス AA のコンストラクタは都合 1 度しか呼び出されません。
具体的なポインタを示せないんだけど,確か,このような場面において余計なオブジェクトの生成を抑制すべくコンパイラが気を利かせてもよい事になっていたかと。
あー,1 年て速いですなぁ。。。
もう既に JPEG デコーダ ver1.0 は終了したのに,この雑記のタイトルはまだ「JPEG デコーダ作製変」なので萌え要素。企画モノなんてこんなもんです。
ToolbarWindow32 を表示してみたんですよ。親フォームの WM_CREATE で CreateWindowEx ( TOOLBARCLASSNAME ) を行います。具体的にはこんな感じ。
case WM_CREATE: {
toolbar = ::CreateWindowEx (
WS_EX_TOOLWINDOW,
"ToolbarWindow32",
"",
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
mainform,
NULL,
::GetModuleHandle(0),
0 );
return 0;
}
まず表示されたものは特にどうって事はありませんでした。ここで親フォームのサイズを変えてみたんですよ。
->オーマイガッ;
分かるかなあ? ツールバーのサイズが変わっていません。まあ解決策はそんなに難しいものでもなく,親フォームに送られて来た WM_SIZE メッセージを Toolbar にも伝えてやればよいわけで
case WM_SIZE: {
if (toolbar) ::SendMessage( toolbar, WM_SIZE, wParam, lParam );
return 0;
}
あーよかった。これに気づかなければここで 1 週間くらい無駄な時間を費やしているところでした。今までのパターンから言えば。
昨日のアプリケーションで,フォントを自由に設定できれば楽しそうです。クライアント領域の上あたりにドロップダウンコンボボックスがあればよさげ。これを Windows ネイティブの機能で実現するには丁度「ToolbarWindow」「ToolbarWindow32」という便利なウィンドウクラスがあります。
Google で「ToolbarWindow」を検索:
->こんなん出ましたけど;
先頭に「ToolbarWindow」というそのまんまのタイトルを持つサイトが検索されました。ていうか GO!
->なんじゃこりゃ?;
FREIWILLIGE FEUERWEHR MOSCHENDORF って何よ。こんなものをトップに載せる人だとは思いませんでした Google 先生。もうアタシ最近どことなくリニューアルした ( らしい ) yahoo に鞍替えしちゃいますよ? 今年のエイプリルフールは 4 月 2 日の直前で終わった事に気づくべきですよ Google 先生?
しかし私の中での yahoo のイメージの大暴落はどこから来るのだろうと考えているわけですが,きっと Y!BB 駅前勧誘大作戦で通行人の UZEEE マシンガン集中砲火を受けた孫・ザ・グレートのせいだという事で一件落着。ええっと,訴えないで>孫大先生
で,作ってみたのがこんなプログラム。
->ユニコードビューア ver1.0 とスクリーンショット;
むちゃくちゃ簡単なソースです。文字の下の数字は文字コード。描画にまつわるサイズの計算はいい加減なんだけど,それなりに良さげなんですね・・・。
ソースコードを見れば分かる (?) ように,"Arial Unicode MS" フォントを決め打ちしています。なくてもデフォルトのフォントを用いて可能な限り正しく表示されますが,フォントはあった方が感動。さてさて,このフォントを自由に設定したくなったわけですが・・どうしようかなあ?
JPEG デコーダも一通り作り上げてしまったのでネタが尽きました。今度もまた企画モノをやろうかなあ? PNG のデコードなんていいかも知れませんね・・・て,Defalte 圧縮形式をデコードできれば簡単やん。。。
そうそう,以前,Unicode を表示するアプリケーションを作りました。Unicode はこんな感じで,本当に簡単に表示できるんですね。
unsigned short c = 0x9304; // 適当なコード
::TextOutW( hdc, x, y, &c, 1 );
ただしそれなりのフォントがインストールされていなければ,この素晴らしさに感動する事はできません。フォントはどこで手に入るのか? ヒントは "arialuni.ttf" というファイルですが,何の事だか分からなければ Google や alltheweb で検索すべきです。笑い
今日,室内の最高気温が摂氏 30 度を越えました。今年の最高記録です。真冬の最低気温は 6 度だったっけ。この 24 度の差は何を意味するでしょう?
部屋の容積が四畳半 x 2.3m = 25.047 立方メートルとします。空気の容積がこれくらいね。そして今日の湿度は大体 70% くらい? もちろん「部屋の容積の 70% が水!」だったら私は溺れてしまうわけで,この「70%」は「飽和水蒸気量」に対する比率。気温 30 度において飽和水蒸気量はおよそ 30.38g/m^3。すなわち 25.047m^3 の空間には最大 760.92g の水を含む事ができ,そして今日は実際には 532.644g の水が含まれていたという計算です。
532g の水を 24 度上昇させるのに必要な熱量は 12768cal。1kg の物体を 5451.1033m 移動させます。現在の私の体重は 45kg なので,もしこの熱量を利用する事が出来れば,動ける距離は 121m ですか。
残念ながら 6 度から 30 度まで上昇するのにどれだけの時間がかかるかのデータはありません。よって結論もナシ。しっかしこの熱量,なんとかして有効利用できないのかなあ。。。
というわけで,こんなのが出来上がりました。
->JPEG デコーダ;
jdec.exe は JPEG::Decoder を用いたサンプルアプリケーションです。インストール作業は必要ありません。コンソールアプリケーションであり,起動するとメインのウィンドウの他にコンソールウィンドウも表示されます。
ソースコードは idct.h および idct.c を除いて K.INABA さんの NYSL をちょっとだけ改変したライセンスの元で配布されています。詳細は 2003-02-28 の雑記を参照のことね。
アルゴリズムの勉強にはなると思いますが,速度は気にすんな。
おとといの雑記で書くの忘れていました:WM_MOUSEWHEEL に対応するにはむちゃくちゃ簡単なコードでよかったわけですが,しかし罠が潜んでいます。次のコードでは,スクロールバーがちょっと挙動不審です。
if (msg == WM_MOUSEWHEEL) {
setVScrollPos( getVScrollPos() - HIWORD(wParam) );
return 0;
}
ここでユーザが「もっと下の画像を観よう」とした場合,つまり画像の表示位置を上にずらす場合,HIWORD(wParam) は負の値をとることが期待されます。ここで HIWORD(wParam) のビットパターンは例えば 111111111110000 など。しかしこのまま getVScrollPos() と演算させると,ビットパターンは 00..( 先頭 16 ビットが 0 )..111111111110000 と拡張されてしまい,意図した動作をしないのです ── 本当ならば 11..( 先頭 16 ビットが 1 )..111111111110000 となって欲しいのに。
マクロ HIWORD() はその名の通り,WORD 型を返してくれます。WORD 型とは windef.h ファイルで定義されているように unsigned short 型。これを 32 ビットに拡張すると,上位 16 ビットは必ず 0 になっちゃいますわな。これを解決するために,おとといのコードのように HIWORD(wParam) の結果を signed な short 型にキャストしてやる必要があるのです。
ああ,常識的な人ならこうするのかなあ?
if (msg == WM_MOUSEWHEEL) {
short delta = HIWORD( wParam );
setVScrollPos( getVScrollPos() - delta );
return 0;
}
これなら HIWORD() の罠にも引っかからないですよねー。。。
本日はちょっとメモ程度。・・おっかしいなあ・・・私の VC++5.0 付属 "spy++" では WM_MOUSEWHEEL メッセージを捕捉する事ができません。これでは,昨日書いた「タッチパネルの下辺をさすって横スクロール」がどのようなメッセージで実現されているかも捕捉できていないかも知れません。
しかしながら spy++ を見ていると突然 "プログラマの勘" 発動。思うところが出てきたので WM_MOUSEHWEEL さえも対応していないプログラムを実行してみます。このプログラムではマウスホイールを転がしても,横スクロールはおろか縦方向のスクロールも動きません。
さて,このプログラムでタッチパネルの右辺や下辺をさすってみると・・・あ,スクロールするし?
ここで結論。タッチパネルの右辺や下辺は,WM_MOUSEWHEEL などのちょっと特殊なメッセージを発行しているのではなく,WM_HSCROLL や WM_VSCROLL をダイレクトに発行しています。いじょ。
ホイールなどのイベントに対応するには,少なくとも手持ちの VC++5.0 では若干の工夫が必要です。具体的にはこんな感じ。
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x400
#endif
#include <windows.h>
windows.h ファイルをインクルードする前に定数 _WIN32_WINNT を 0x400 以上の値で定義してやります。そうでなければ WM_MOUSEHWEEL が定義されないのです。
ウィンドウプロシージャの中で WM_MOUSEWHEEL に反応するコードを書けば,とりあえずホイール対応のアプリケーションの完成です。・・・かな?
if (msg == WM_MOUSEWHEEL) {
setVScrollPos( getVScrollPos() - (short)HIWORD(wParam) );
return 0;
}
これだけ。
ただし,これは縦スクロールにしか対応していません。例えば私が使っているノートパソコン "vaio" ( sony 製 ) は,タッチパネルの下辺をさすってやると横スクロールも可能です。WM_MOUSEWHEEL のパラメータには縦/横を区別する値が見られませんが,これは一体?
ユーザのアクションに対するイベントを捕捉,処理してみます。ちょこまかと面倒臭かったりするわけですが,これくらいは我慢するべき?
例えば横スクロール時に送られて来る WM_HSCROLLメッセージは wParam の下位 16 ビット LOWORD( wparam ) によりいくつかに解れますが,とりあえず
SB_LINELEFTSB_LINERIGHTSB_PAGELEFTSB_PAGERIGHTSB_THUMBTRACKに対応させておけば,今日びのアプリケーションとしては立派なものになります。SB_ENDSCROLL は対応する必要はないと思うんだけどどうでしょう? SB_THUMBTRACK でリアルタイムに画面を更新できない場合 ( 画面の描画が重い場合など ) には有効に利用すべきです。
// 横スクロール時
if (msg == WM_HSCROLL) {
switch (LOWORD( wParam )) {
case SB_THUMBTRACK: {
setHScrollPos( HIWORD( wParam ) );
return 0;
}
case SB_LINELEFT: {
int pos = getHScrollPos();
if (pos) {
--pos;
setHScrollPos( pos );
}
return 0;
}
case SB_LINERIGHT: {
int pos = getHScrollPos();
pos++;
setHScrollPos( pos );
return 0;
}
case SB_PAGELEFT: {
int page = getClientWidth();
int pos = getHScrollPos();
if ( page < pos ) pos -= page;
else pos = 0;
setHScrollPos( pos );
return 0;
}
case SB_PAGERIGHT: {
int page = getClientWidth();
int pos = getHScrollPos();
if ( page < image.getWidth() - pos ) pos += page;
else pos = image.getWidth() - page;
setHScrollPos( pos );
return 0;
}
}
return 0;
}
image オブジェクトは表示する画像を保持します。getWidth() メソッドでその幅を取得しています。getClientWidth() はウィンドウのクライアント部分の幅を返す関数。それ以外の関数はおととい〜昨日の雑記で実装したものです。
そういえば,入力デバイスによってはホイール ( やパネル ) を操作してスクロールできたりするんですよね。WM_MOUSEWHEEL にも対応させてやる必要がありますか。
スクロールできるウィンドウを実装するにあたり,面倒な部分はほぼ Windows が面倒をみてくれます。スクロール位置をはみ出しそうになったらどうするのか,などの細かい部分も含めてね。おかげでこちらはほぼ何も考えずに API を呼び出すだけ。
昨日のインタフェースの実装は,こんな感じでどうでしょ。
SCROLLINFO si_;
// フォームクラスの初期化時に SCROLLINFO::cbSize メンバを初期化しておきます
void onInitialize() {
si_.cbSize = sizeof( SCROLLINFO );
}
//! スクロールの幅を設定
void setHScrollRange( int min, int max ) {
si_.fMask = SIF_RANGE;
si_.nMin = min;
si_.nMax = max;
::SetScrollInfo( getWindowHandle(), SB_HORZ, &si_, TRUE );
}
//! 現在フォームに見えている幅を設定
void setHScrollPage( int page ) {
si_.fMask = SIF_PAGE;
si_.nPage = page;
::SetScrollInfo( getWindowHandle(), SB_HORZ, &si_, TRUE);
}
//! スクロールの位置を設定
void setHScrollPos( int pos ) {
si_.fMask = SIF_POS;
si_.nPos = pos;
::SetScrollInfo( getWindowHandle(), SB_HORZ, &si_, TRUE);
InvalidateRect( getWindowHandle(), 0, FALSE );
}
//! スクロールの位置を取得
int getHScrollPos() const {
si_.fMask = SIF_POS;
if (::GetScrollInfo( getWindowHandle(), SB_HORZ, &si_ )) {
return si_.nPos;
}
return 0;
}
以上で横スクロールバーのためのメソッド setHScroll***() の実装は終わり。縦スクロールバーのためのメソッド setVScroll***() は,中身の定数 SB_HORZ を SB_VERT にするだけなので簡単です。また getWindowHandle() メソッドは ::CreateWindowEx() で生成されたウィンドウハンドルを得るゲッタです。2003-04-07 でアプしたコードでは MainForm クラスが windowHandle メンバを public で持っていますが,これって行儀悪いですよね。。。
明日はスクロールバーに対するイベントを扱ってみようと思います。スクロールバーに対するユーザの操作はいくつか種類があるので,ちょっと面倒くさいんですよねー。。。
昨日やっとの思いで完成させた JPEG デコーダ,大きな画像は全体を表示する事が出来ません。だせぇ。全体を表示するために,アプリケーションにスクロールバーをつけてみる事にしました。
インタフェースとして,こんなメソッドがあれば良い感じだと思いませんか?
interface Scrollable {
void setHScrillRange( int min, int max );
void setVScrollRange( int min, int max );
void setHScrollPage( int page );
void setVScrollPage( int page );
void setHScrollPos( int pos );
void setVScrollPos( int pos );
int getHScrollPos() const;
int getVScrollPos() const;
};
setHScrollRange, setVScrollRangesetHScrollPage, setVScrollPageWM_SIZE イベントに反応してやる事が大切。setHScrolllPos,setVScrollPosgetHScrollPos,getVScrollPosというわけで,明日はこのインタフェースをフォームクラスに接続して実装してみます。すんなり成功すればお慰みですが,まぁうまくいきっこないですわな〜(マイナス志向