pointer to overloaded function

最初どうするんだろうと思ったけど C では「関数の pointer 」型を宣言するのに戻り型と引数を指定していたし C++ compiler は signature でどの関数が呼ばれているかを判断しているわけなのでじゃあ signature を構成する引数と ( member function の場合 ) cv 修飾子 ( と C++0x 以降は ref 修飾子も ? ) を指定すればとれるんじゃねと予想をつけたら当たった。 C++0x draft の 13.4 Address of overloaded function が元ネタ ( 俺日本語訳 -> http://github.com/januswel/Cxx0xISja/blob/master/13.4%20Address%20of%20overloaded%20function%20%5Bover.over%5D.txt ) 。

void f(double) {
    // ...
}

void f(int) {
    // ...
}

struct s {
    void f(double) {}
    void f(int) {}
};

int main(void) {
    void (*pfi1)(int)           = &f;       // f(int)
    void (*pfd1)(double)        = &f;       // f(double)
    void (*pfi2)(int)           = f;        // f(int)
    void (*pfd2)(double)        = f;        // f(double)

    void (s::*psfi1)(int)       = &s::f;    // s::f(int)
    void (s::*psfd1)(double)    = &s::f;    // s::f(double)
    void (s::*psfi2)(int)       = s::f;     // error: pointer to member
    void (s::*psfd2)(double)    = s::f;     // error: pointer to member
}

上記の例だと一番下の 2 つのみが error になる。これは member の address は qualified-id の頭に & をつけた書き方でしか取得できないという別の rule が絡んでいるため ( http://d.hatena.ne.jp/janus_wel/20100502/1272810795 ) 。

template<typename T>
struct s2 {
    void f(T) {}
};

struct s3 {
    template<typename T>
    void f(T) {}
};

template<typename T>
struct s4 {
    template<typename U>
    void f(T, U) {}
};

int main(void) {
    void (*pft1)(char)  = &f<char>;
    void (*pft2)(int)   = &f<int>;
    void (*pft3)(char)  = f<char>;
    void (*pft4)(int)   = f<int>;

    void (s2<char>::*pstf1)(char)   = &s2<char>::f;
    void (s2<int>::*pstf2)(int)     = &s2<int>::f;

    void (s3::*psft1)(char) = &s3::f<char>;
    void (s3::*psft2)(int)  = &s3::f<int>;

    void (s4<char>::*pstft1)(char, int) = &s4<char>::f<int>;
    void (s4<int>::*pstft2)(int, char)  = &s4<int>::f<char>;
}

さらに template が絡んだ場合は引数の指定がないので推定が働かないわけで、上記のように明示的に template parameter を与えて目的のものを実体化させてやらないと怒られまくる。でもまぁめんどくさいだけで実体化させてやれば OK ということを覚えておけば問題ないはず。ちなみに上記は書き方は全部通る。

#include <algorithm>
#include <vector>
#include <string>
#include <functional>

int main(const int argc, const char* argv[]) {
    std::vector<std::string> ss(argc);
    std::copy(argv, argv + argc, ss.begin());

    std::vector<int> is(argc);
    std::transform(
        ss.begin(), ss.end(), is.begin(),
        std::bind2nd(
            std::mem_fun_ref(
                static_cast<int (std::string::*)(const char*) const>(
                    &std::string::compare)),
            argv[0]));
}

最後、必ずしも変数に格納しないと目的の関数がとれないわけではなくて上記のように cast 構文を使うことで選択することもできる。これは C like な cast 構文 ( 上記の例で言うと [](int (std::string::*)(const char*) const)&std::string::compare[] ) でも OK みたいなんだけどただでさえカッコが多い関数 pointer の式にわかりにくいカッコが追加されるので上記のように static_cast を使うのが無難だと思う。で、こういった書き方は cast を使ってはいるものの関数の selector として理解するのが妥当、と ( 多分 & 演算子で address を取った時点では void * 型でそいつを明示的に cast することで目的の型にしているだけだと思う ) 。

ただまぁよほど変な理由がない限り一時的に変数として保持してそいつを使うほうが読みやすいと思う ( 説明的変数 ) 。どうしようもなく cast したい場合でも事前に typedef しておいてできるだけ読みやすくするとかのアレもするべき。

typedef int (std::string::*const_char_arg)(const char*) const;
std::vector<int> is(argc);
std::transform(
    ss.begin(), ss.end(), is.begin(),
    std::bind2nd(
        std::mem_fun_ref(static_cast<const_char_arg>(&std::string::compare)),
        argv[0]));