2014年4月10日木曜日

MSVC11で、指定型について特定のシグニチャを持つメンバ関数があるかどうか調べるメタ関数を実装した

BOOST_TTI_HAS_MEMBER_FUNCTION使え(ぉ

派生が絡むとちょっと面倒くさかった。結果として以下の様な感じに。

template <typename T>
struct has_xxx
{
private:
    typedef char yes_t;
    typedef double no_t;

    template <typename F, F> struct Sfinae;
    template <typename U, void (U::*P)() const>
    struct Sfinae<void (U::*)() const, P> {};

    template <typename U> static yes_t check(Sfinae<decltype(&U::xxx), &U::xxx>*);
    template <typename U> static no_t check(...);

public:
    static bool const value = sizeof(yes_t) == sizeof(check<T>(0));
};

Sfinaeの特殊化でシグニチャを調整すればOK。この実装ならxxxが親クラス側で実装されていても正しく判定できる。

2013年12月26日木曜日

is_enum改良版

gcc2.7.2でis_enumを作る取り込み・・・一応改良版ができた。

template <typename T>
struct is_enum_check
{
    is_enum_check(T const&);
    static yes_type check(int);
    static no_type check(is_enum_check const&);
};

template <typename T>
struct is_enum_impl
{
  enum
  {
    value1 = (sizeof(yes_type) == sizeof(is_enum_check<T>::check(*((T*)0)))),
    value2 = !is_primitive<T>::value,
    value3 = value1 && value2
  };
};

template <typename T>
struct is_enum
{
  enum
  {
    value = is_enum_impl<T>::value3
  };
};

肝はis_enum_checkのコンストラクタでexplicitにしないことと、このオブジェクトアダプタをis_enum_checkと別体にしないこと。別体にすると不可思議なコンパイルエラーになった。

is_enumやっぱダメだわー

non-PODが全部 '...' 渡しで引っかかる。

今後の方針としては

  • 別の実装方法を考える。
  • 警告の無効化手段を考える・・・ないっぽいが。
  • 諦める。
諦めるが最有力。

2013年12月19日木曜日

is_enumは概ね実装できていた / gcc2.7.2の特徴

昨日のコンパイルエラーは結構特殊な状況下でしか出ないことがわかった。
回避策もあったのでよしとしよう。

気を良くして昨日のコードを説明。

■状況
今はVxWorks5.4で動くコードを書くのが仕事である。で、それで動く最新のコンパイラがgcc2.7.2なのである。
もしかしたらWindRiverに言えば高いお金を払ってバージョンアップできるのかもしれない。あるいは自分でクロスコンパイラをビルドできるのかもしれない。でも情報が少ないので諦めている。
clangか何かでC++14コードをC89コードに変換できないかなぁ。

■gcc2.7.2(というかVxWorks5.4 + g++での開発環境)の特徴
この環境ではC++98規格には準拠していない。コンパイラのリリースが1989?だし・・・。

・namespaceが使えない
どうもまったく完全に実装されてないわけではないらしい。というのは、std名前空間にあるクラスを派生させるとICE(Internal Compiler Error)になるケースがあるからである。例えばstd::exceptionとか、自作std::integral_constantとか。しかし自前ではnamespaceを定義できない。

・整数定数がたまに使えない
どういう条件でダメになるのかわからないが、
class Hoge
{
    static bool const value = false;
};
とかがダメなケースがある(ICE発生)。なのでenumで代用することになる。
class Hoge
{
    enum
    {
        value = false
    };
};

・テンプレートは使える
以下に述べる通りバグは多数だが、必要最小限には使える。部分特殊化や整数引数も使えるので、ちょっとしたTMPも可能。placeholderなしのstd::function / std::bindっぽい機能も実装できた。

・メンバ関数テンプレートでは特殊化のために型を指定できない
以下のようなコードはNG。
struct Hoge
{
    template <typename T>
    void temp();
};

void doSomething()
{
    Hoge h;
    h.temp<int>();
}
これはstaticメンバであってもダメ。グローバル関数テンプレートならOK。
template <typename T>
void temp();

void doSomething()
{
    temp<int>();
}

・インナークラステンプレートはNG
以下は両方ともダメ。
template <typename T>
struct Outer
{
    template <typename U>
    struct Inner{};
};

struct Outer
{
    template <typename T>
    struct Inner{};
};
あとアウターがテンプレートのとき、インナーでそのテンプレート仮引数シンボルを解釈できないことがある。そのときはアウター側でtypedefすれば回避できる。
これがダメなのでstd::allocatorのrebindがダメになる。というかstd::allocatorの実装がヘン。よって組み込み向けのコンテナや文字列クラスなど、メモリ確保をプールやスタックにやらせる場合はすべてコンテナごと自作となる。まあstd::mapとか実装されてないし・・・。

・可変個数引数(...)とテンプレートを組み合わせると壮大にICEする
しかもコンパイルエラーになる箇所が意味不明なことが多い。あるいはコンパイルエラーにならずにバグったコードを吐くこともある。
# 可変個数引数とか言ってすみません。MSのsprintf_s()の代替とか要望されたのです。

・SFINAE使えない
まあ期待するほうが無理ってものだが、type_traits系を実装するのが大変すぎる(というか一部は無理ぽ・・・)。

・STL使えない
なんか色々と残念な感じ。iterator_categoryはOK。mapは使えない、listも標準に準拠しない動作がある。まあallocatorがダメな時点でSTLはほとんど全部書き直すのだけど。

・friendがおかしいことがある
どうおかしいのか詳細を忘れたが、とにかくなんかおかしいことがあった。大抵は期待通りになる。

・演算子オーバーロードの適用でICEになることがある
特にテンプレートが絡むとかなりの確率でICEになる。まったく無関係になりそうなものでも巻き込まれる。なのでVariant的な型を作るとき大変危険、というか作らないほうがよい。

■is_enumの実装
必要としていたのは完全版じゃなくて、

  • プリミティブ型
  • enum
  • クラス/構造体
が識別できればよかったので、そのように実装してみた。大変なのはenumとクラスの識別で、「メンバを保持する」的なSFINAEの書き下しが使えない以上、それ以外の手段で識別するしかない。

そこで「intに暗黙の型変換可能かどうか」で識別してみた。これだとoperator int()があるクラスは引っかかってしまうが、そこはもう諦めた。内輪の事情でoperator int()を実装することがほぼないとわかっていたので。

2013年12月18日水曜日

gcc2.7.2でis_enumを実装したかった・・・

どうしてもコンパイラ警告が消えないので、諦めモード・・・。
SFINAEとか使えないのが痛すぎる。


■現下の実装
template <typename T> struct is_primitive { enum { value = 0 }; };
template <> struct is_primitive<void> { enum { value = 1 }; };
template <> struct is_primitive<bool> { enum { value = 1 }; };
template <> struct is_primitive<int> { enum { value = 1 }; };
template <> struct is_primitive<unsigned int> { enum { value = 1 }; };
template <> struct is_primitive<char> { enum { value = 1 }; };
template <> struct is_primitive<signed char> { enum { value = 1 }; };
template <> struct is_primitive<unsigned char> { enum { value = 1 }; };
template <> struct is_primitive<short> { enum { value = 1 }; };
template <> struct is_primitive<unsigned short> { enum { value = 1 }; };
template <> struct is_primitive<long> { enum { value = 1 }; };
template <> struct is_primitive<unsigned long> { enum { value = 1 }; };
template <> struct is_primitive<float> { enum { value = 1 }; };
template <> struct is_primitive<double> { enum { value = 1 }; };

typedef char yes_type;
struct no_type { char dummy[8]; };
yes_type enum_check(int);
no_type  enum_check(...);

template <typename T>
struct is_enum_impl
{
  enum
  {
    value1 = (sizeof(yes_type) == sizeof(enum_check(*((T*)0)))),
    value2 = !is_primitive<T>::value,
    value3 = value1 && value2
  };
};

template <typename T>
struct is_enum
{
  enum
  {
    value = is_enum_impl<T>::value3
  };
};
■出る警告
warning: cannot pass objects of type `(構造体/クラス名)' through `...'

■ダメな実装
(1) template <typename T> no_type check(T*, void (T::*)() = 0);
(2) yes_type check(...);
とか書くと、enumの型で(1)が適用される。

2012年8月8日水曜日

Windows7においてMicrosoft Updateを手動で有効にする

前回の記事にしたがってUsersをDドライブに移動すると、Microsoft Updateをインストールできないという不具合が出た。


とりあえず、以下の手段で手動でMicrosoft Updateを有効にした。

(1)
MUAuth.cabをダウンロードし、Cドライブ直下に配置する。

(2)
メモ帳などに以下のスクリプトを入力し、Cドライブ直下に拡張子.vbsで保存する:


Set ServiceManager = CreateObject("Microsoft.Update.ServiceManager")
ServiceManager.ClientApplicationID = "My App"

'add the Microsoft Update Service, GUID
Set NewUpdateService = ServiceManager.AddService2("7971f918-a847-4430-9279-4a52d1efe18d",7,"")
例として、このスクリプトをmu.vbsという名前で保存する。

(3)
コマンドプロンプトを開いて以下のコマンドを実行:
>cd \
>cscript mu.vbs


参考:


MBA2012のbootcampでUsersフォルダをSDカードに移動

Macbook Air 2012(たぶん2012以外も)でbootcampすると、Windowsが読み書き可能なDドライブはSSD上には作れない。これは以下の理由による:
  • bootcampすると、SSDにはパーティションが4つある状態になる。
  • bootcampはSSDの最後のパーティションにWindows起動ドライブがなければならない仕様。
  • MAC OS Xが普段は利用しないパーティション(=SSDの最後のパーティションではないパーティション)を潰してWindows用パーティションにすると、MAC OS Xのアップデート時などでリカバリパーティションが再生されるために潰される。
一方、自分はWindows環境を構築する際UsersフォルダをDドライブに移動している。これは、よく書き換えるドライブをシステムドライブと分けることで、バックアップを楽にしたり、トラブル時の復旧を早くするためである。 とあるプログラマの休日 - Windows7でUsersを移動 などを参照のこと。

さて、この度なんとかUsersフォルダを内蔵SDスロットに挿したSDカードに退避させることに成功したので、その手順をメモしておく。


■参考にしたサイト


■用意するもの

■事前準備
bootcampの一般的な注意点となるが、Appleのサイトからインストールマニュアルを落として良く読んでおく。あとMAC OS Xはユーザーデータのバックアップをきちんとして、万一のために外部メディアで再インストールできるようにしておく。ディスクユーティリティでMountain LionのInstallESD.dmgをDVD-R DL等に焼いておくとよい。


■手順
(1)
MAC OS X上でディスクユーティリティを使ってSDカードをフォーマットする。今回はThunderbirdのデータをMACとWINの両方で読みたかったので、exFATでフォーマットした。

(2)
SDカードをスロットから取り除く。自分の環境ではこの手順が抜けると、Windowsのインストールにてディスク0パーティション4が(フォーマットをしても)インストール先として選択できなかった。

(3)
bootcamp開始。
Windowsの.isoをMAC OS X上にコピーした後、MAC OS X上でbootcampアシスタントを起動し、「Windows7インストールディスクを作成」、「最新のWindowsサポートソフトウェアをAppleからダウンロード」、「Windows7をインストール」の3つにチェックを入れて実行。

(4)
Windowsのインストーラが起動し「Windowsのインストール場所を選択してください。」に来たら、ディスク0パーティション4(BOOTCAMPとラベリングされているはず)をフォーマットしてインストール先とする。フォーマットするには「ドライブオプション(詳細)」をクリックするとメニューが出る。

(5)
ユーザー名とコンピューター名を入力するところまで進んだら、SDカードを挿し、SHIFT+F10を押す。コマンドプロンプトが立ち上がる。
※ファンクションキーにfnキー同時押しが必要なことがあるので、コマンドプロンプトが起動しなかったらSHIFT+fn+F10を押下する。

(6)
あとでログイン画面でログインせずにコマンドプロンプトを利用したいので、ユーティリティマネージャをコマンドプロンプトに置き換える:
>ren Utilman.exe Utilman.exe.back
>copy cmd.exe Utilman.exe
※あとでユーティリティマネージャを復活させたくなったら、LinuxのLive CDなど、Windowsを起動せずにSSD内のNTFSパーティションを書き換える手段が必要。

(7)
ディスクの管理を起動:
>diskmgmt.msc
「仮想ディスク サービスに接続中」が結構長く表示されることがあるが、我慢強く待つ(10秒~1分程度)。

(8)
少なくともSSD、SDカード、USBメモリ(bootcampでのWinインストール用)の3つが認識されているはず。ここでSDカードのドライブレターがDになっていなければ、Dにする。ちなみにUSBメモリのドライブレターは変更してもWindowsのインストールに影響しない。SDカードのドライブレターがDになったら、ディスクの管理を閉じる。
※このとき右クリックができないし、右クリック代わりのショートカット(SHIFT+F10)はコマンドプロンプトが起動してしまうので、メニューの「操作(A)」から変更する。

(9)
以下のコマンドによりUsersを移動する:
>robocopy "C:\Users" "D:\Users" /E /COPYALL /XJ
>rmdir "C:\Users" /S /Q
>mklink /J "C:\Users" "D:\users"

(10)
以下のコマンドによりProgramDataをコピーし、削除を試みておく:
>robocopy "C:\ProgramData" "D:\ProgramData" /E /COPYALL /XJ
>rmdir "C:\ProgramData" /S /Q
この段階ではC:\ProgramDataは利用中なので、rmdirは一部失敗するが無視する。

(11)
コマンドプロンプトからレジストリエディタを起動する:
>regedit

(12)
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\ProfileList
の下にある4つのキー値について、「%SystemDrive%」を「D:」に変える。
Default           -> D:\Users\Default
ProfilesDirectory -> D:\Users
ProgramData       -> D:\ProgramData
Public            -> D:\Users\Public

(13)
日本で作業している場合は以下を行なってハードウェア時刻をUTC扱いにする:
HKEY_LOCAL_MACHINE¥SYSTEM¥CurrentControlSet¥Control¥TimeZoneInformation
の下に「RealTimeIsUniversal」の名前でDWORD値を作り、値を1にする。

(14)
レジストリエディタとコマンドプロンプトを閉じ、Windowsの初期設定を完了させる。日本で作業している場合は日付と時刻の設定において9時間前の時刻が表示されるが、無視する(設定しなおさない)。

(15)
Windowsを再起動させるが、ログインしない。ログイン画面でcommandキー+Uキー(つまりWindowsキー+Uキー)を押下するとコマンドプロンプトが起動するので、以下を実行してProgramDataを移動:
>rmdir "C:\ProgramData" /S /Q
>mklink /J "C:\ProgramData" "D:\ProgramData"

(16)
コマンドプロンプトを閉じて、ログインする。

(17)
bootcampドライバをインストール。USBメモリのWindowsSupportフォルダ内にあるsetup.exeを実行する。インストール後に再起動するが、ログインしない。
※bootcampドライバによってMAC OS XのインストールパーティションがDドライブになってしまっているはず。なのでここでログインすると、SDカードに移動したUsersをロストしてしまう。こうなると、色々と面倒な復旧作業を行う必要が出てしまう(単にドライブレターを戻すだけでは復旧しない)。

(18)
ログイン画面でcommand+Uキーを押下してコマンドプロンプトを起動し、ディスクの管理を起動する:
>diskmgmt.msc

(19)
SDカードのドライブレターを再びDに変更し、ディスクの管理とコマンドプロンプトを閉じる。

(20)
ログインせずに再起動。
※もしかしたら、このステップは不要かもしれない・・・が、失敗しているとここまでの苦労が完全に水の泡なので、怖くてログインできない・・・。

(21)
ログインして動作を確認する。


いやー、面倒だ!