本の虫

著者:江添亮
ブログ: http://cpplover.blogspot.jp/
メール: boostcpp@gmail.com
Twitter: https://twitter.com/EzoeRyou
GitHub: https://github.com/EzoeRyou

アマゾンの江添のほしい物リストを著者に送るとブログ記事のネタになる

筆者にブログのネタになる品物を直接送りたい場合、住所をメールで質問してください。

メンバー関数へのポインターを返すメンバー関数へのポインターを返すメンバー関数

class Foo;が存在したとして(1)Fooのメンバ関数ポインタ(2)を戻すメンバ関数のポインタが欲しいと思った(なお(1)で戻すメンバ関数もFooのメンバ関数ポインタを戻す)のだが、どうあがいても記述出来ないものだったりするのだろうか?

ようするに、以下のようなことがしたいわけだ。

class Foo
{
public :
    // メンバー関数a
    void a() { }
    // メンバー関数aへのポインターを返すメンバー関数b
    ??? b() { return &Foo::a ; }
    // メンバー関数aへのポインターを返すメンバー関数bへのポインターを返すメンバー関数c
    ??? c() { return &Foo::b ; }
}

ここで、???の部分に戻り値の型を記述しなければならない。

もちろんこれは記述できる。ただしその記述は、C++の規格のバージョンにより難易度が異なる。

C++14

最新の素晴らしい標準規格であるC++14では、この程度の問題は赤子の手をひねるより簡単だ。

C++14に追加された戻り値の型推定は、戻り値の型を書くべき場所にautoキーワードを書くことで、return文のオペランドの式の型を戻り値の型として書いた扱いになる。

// C++14
// 戻り値の型推定
class Foo
{
public :
    // C++14
    void a() { }
    auto b() { return &Foo::a ; }
    auto c() { return &Foo::b ; }
} ;

return文のオペランドの式の型はコンパイル時に決定できるため、当然、戻り値の型もコンパイル時に決定できる。これは具体的な形名を手で書くのと全く同じである。コンパイラーができることはコンパイラーにやらせれば良い。人間様が手をわずらわす必要はない。

C++11

残念ながら、4年も前の大昔の標準規格であるC++11には戻り値の型推定がない。そのため、戻り値の型を手で書かなければならない。ただし、C++11には、戻り値の型を後置する新しい関数記法がある。戻り値の型を書くべき場所にautoキーワードを書き、関数宣言の最後に->を書いて、その後に戻り値の型を書く。

// C++11
// 新しい関数記法
class Foo
{
public :
    void a() { }
    auto b() -> void (Foo::*)() { return &Foo::a ; } 
    auto c() -> auto (Foo::*)() -> void (Foo::*)() { return &Foo::b ;}
} ;

関数aの型は、void (Foo::*)()である。あるいは、auto (Foo::*)() -> voidである。とすると、この型を返す関数は、auto b() -> void (Foo::*)()となるここまでくればもう明白だろう。そう、関数cが返すのは auto (Foo::*)() -> ???で、???に入る戻り値の型はvoid (Foo::*)()だ。

C++03

C++03を今使うものは何か苦行でも行っているとしか思えない。

まず、関数型がある。


void (int)

しかる後に、関数へのポインター型がある。


void (*)(int)

関数ポインターを返す関数は以下のように書ける。


void f(int) { }

void (* g())(int)
{
    return &f ;
}

わかるだろうか。g()が関数gの関数名と仮引数リストだ。void (* ...)(int)の部分が戻り値の型だ。関数型には仮引数リストやその他の修飾などが含まれる。関数の型の文法上、仮引数リストと修飾はg()を囲む形で記述される。

つまりこういうことだ。

void (* // 戻り値の形名
    g() // 関数名と仮引数リスト
)(int) // 戻り値の形名
;

関数gへのポインターを返す関数hは以下のように書ける。


void (* (* h())() )(int)
{
    return &g ;
}

つまりこういうことだ。

void (* // 戻り値の形名
    (*  // 戻り値の形名
        h() // 関数名と仮引数リスト
    )() // 戻り値の形名
)(int)  // 戻り値の形名
;

そして、メンバーへのポインター型がある。

struct X { void f() ; } ;

void (X::* p1)() = &X::f ;

これらを組み合わせると、C++03という化石の様な古代の標準規格でも書くことができる。

// C++03
class Foo
{
public :
    // C++03
    void a() { }
    void (Foo::* b())() { return &Foo::a ; }
    void (Foo::* (Foo::* c())())() { return &Foo::b ;}
} ;

typedefを使うことで、いくらかマシにはできる。

class Foo
{
public :

    typedef void (Foo::* a_ptr) () ;
    typedef a_ptr (Foo::* b_ptr)() ;

    // C++03
    void a() { }
    a_ptr b() { return &Foo::a ; }
    b_ptr c() { return &Foo::b ;}
} ;

結論としては、早くC++14に移行しろということだ。

ドワンゴ広告

この記事はドワンゴ勤務中に書かれた。

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0