Hatena::ブログ(Diary)

C++でゲームプログラミング

2010-10-26

[][]戻り値で SFINAE


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


template<typename T>
void
func(T,
     typename boost::enable_if<boost::is_integral<T> >::type* = 0
){}


// 戻り値型は、enable_if の第二引数
template<typename T>
typename boost::enable_if<boost::is_integral<T>, void>::type
func2(T){}


struct hoge{
    int value;
};

// operator + は、引数が2つしか渡せないので戻り値型で判断する
template<typename T>
typename boost::enable_if<boost::is_integral<T>, hoge>::type
operator +(hoge& lhs, T rhs){
    hoge result;
    result.value = lhs.value + rhs;
    return result;
}


int
main(){
    
    func(10);         // ok
//  func(0.0f);       // error
    func('a');        // ok
    
    func2(10);        // ok
//  func2(0.0f);      // error
    func2('a');       // ok

    hoge h;
    h + 10;           // ok
//  h + 0.0f;         // error
    h + 'a';          // ok

    return 0;
}

template 関数で、T型を制限する場合、SFINAE を使うテクニックがありますが、その戻り値版です。

引数に enable_if を記述するコードはよく見かけるんですが、戻り値に enable_if を記述するコードは見たことがなかったので書いてみました。

operator + などの演算子のオーバーロードは、引数を2つしか渡すことが出来ないので、こういう書き方になるんじゃないでしょか。

個人的にこっちの方がスマートなんだけど何か使われない理由があるか…。

まぁ、複数の条件を付ける場合は、引数で定義したほうが楽そうですね。


[追記]

VC8.0 では、SFINAE が出来ないとコメントを頂いたので試してみたんですが、問題なくコンパイルできたでござる。

試しに VC9.0 も試してみたんですが、こっちも問題なくコンパイル出来ました。

VC7.1 は手元にないので試せない…。

テストコードは、以下のとおり。

template<typename T>
typename boost::enable_if<boost::is_same<T, int>, int>::type
func2(T rhs){
    std::cout << typeid(rhs).name() << ":is int" << std::endl;
    return rhs;
}

template<typename T>
typename boost::enable_if<boost::is_same<T, char>, char>::type
func2(T rhs){
    std::cout << typeid(rhs).name() << ":is char" << std::endl;
    return rhs;
}

template<typename T>
typename boost::enable_if<boost::is_same<T, float>, float>::type
func2(T rhs){
    std::cout << typeid(rhs).name() << ":is float" << std::endl;
    return rhs;
}

[boost]

ver 1.44.0

[参照]

http://d.hatena.ne.jp/faith_and_brave/20081110/1226308533

http://cpplover.blogspot.com/2010/03/decltypesfinae.html

2010-10-24

[][]PImpl

PImpl イディオムは、ヘッダー内での include 数を減らしたい場合や実装を隠蔽する時に使用されるイディオムです。

id:melpon さんの C++ でのビルド時間を短縮するいくつかの方法 にチラッと出てきたアレです。

便乗ヒャッホイ

そういえばあんまり使ったことがないなー、と思いちょっとまとめ。


[*.h]

#ifndef _FILE_H_
#define _FILE_H_

class file{
public:
    file();
    file(const file& rhs);
    file& operator =(const file&);
    ~file();

    void read(const char* file_name);
    void show();
private:
    class impl;
    impl*    pimpl;
};

#endif    // #ifndef _FILE_H_

[*.cpp]

#include "file.h"

#include <vector>
#include <string>

// この中にガシガシと実装していく
class file::impl{
public:
void read(const char* file_name){
    // 〜ファイルの読み込み〜
}
void show(){
    // 〜ファイルの表示〜
}
private:
    std::vector<std::string> line_data;
};


file::file()
    : pimpl(new file::impl()){}

file::file(const file& rhs)
    : pimpl(new file::impl(*rhs.pimpl)){}

file::~file(){
    delete pimpl;
}

file&
file::operator =(const file& rhs){
    impl* tmp = pimpl;
    pimpl = new file::impl(*rhs.pimpl);
    delete tmp;
    return *this;
}

// impl の呼び出しのみ
void
file::read(const char* file_name){
    pimpl->read(file_name);
}
void
file::show(){
    pimpl->show();
}

まぁフタを開けてみれば、そんなに難しい事をやっているわけじゃないですね。

ヘッダー側でインナークラスを宣言して、その中に実装をガシガシと書いて行きます。

別のクラスに実装を記述するという意味では、インターフェースの様な感じかな?

あっちも隠蔽性は高いし。

こうすれば、変数の宣言をヘッダー内でする必要がなくなるので、ヘッダー内での include 数を減らすことが出来ます。


しかしながら引数戻り値型に使う場合は、当然 include が必要となります。

#include <string>

class file{
public:
    // std::string を使いたい
    const std::string& file_name();
};

まぁこれもヘッダー内でクラスの宣言だけ書いておけば解決するんですが…。

string や vector なんかのちょっと複雑な template クラスだと手間なので、今回はやめておきます。


と、まぁざっとまとめればこんな感じですね。

本来であれば、代入演算子やコピーコンストラクタなんかも実装しないとダメなんですが、今回は割愛しました。

※追記しました。

詳細は、↓のページを参照してください。

ファイルの依存関係を減らす事もいいんですが、実装を隠蔽するのが結構面白いです。

今までライブラリなんかを作る場合は、インターフェースを使用していたのですが、こっちの方が楽そうですね。


[追記]

コメントでツッコまれたので、代入演算子とコピーコンストラクタを追記。

やっぱり手抜きはダメですね。

ご指摘ありがとうございます。

これで合ってる…よね?


[参照]

http://ameblo.jp/woooh-p/entry-10053200172.html

http://d.hatena.ne.jp/melpon/20101022/1287705842

2010-09-08

[][]ダブルディスパッチ

なにやら難しい言葉だがやってる事は簡単である。

struct Zaku{};
struct Char_zaku
    : public Zaku{};

struct Gundam{
    void say(const Zaku&) const{
        std::cout << "相手がザクなら!人間じゃないんだ、ぼくだって" << std::endl;
    }
    void say(const Char_zaku&) const{
        std::cout << "撃つぞ・・・撃つぞ・・・撃つぞぉぉぉ!" << std::endl;
    }
};

引数の型によって処理を変更したい場合があるとする。

呼び出し側は以下のようになる。

Zaku          zaku;
Char_zaku     char_zaku;
Gundam        gundam;

gundam.say(zaku);         // Gundam::say(const Zaku&) が呼ばれる
gundam.say(char_zaku);    // こっちも問題なく呼び出される

つまり、ダブルディスパッチとは、実行時の型によって異なる関数を呼び出す為の機構である。

C++だと関数オーバーロードを利用して行っている。

よく理解していなかったので改めて調べてみたら単なるバズワードだった。


また、以下のような場合には注意が必要である。

Zaku&         zaku = Char_zaku();
Gundam        gundam;

gundam.say(zaku);  // Gundam::say(const Zaku&) が呼ばれる      
                   // 本当は Gundam::say(const Char_zaku&) が呼ばれて欲しい

この場合は、自分を渡すような処理を追加して対応する。

struct Zaku{
    virtual void call_say(const Gundam& gundam){
        gundam.say(*this);
    }
};

struct Char_zaku
    : public Zaku{
    virtual void call_say(const Gundam& gundam){
        gundam.say(*this);
    }
};
Zaku&        zaku = Char_zaku();
Gundam        gundam;

zaku.call_say(gundam);    // 問題なく Gundam::say(const Char_zaku&) が呼ばれる

これで問題なく呼ばれる。

続く…?