2015-03-24

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

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

8 comments:

Anonymous said...

>auto b() { return &Foo::a ; }
これってインラインじゃないと無理?

Anonymous said...

関係無いけどC++14で
auto f();
の宣言が許されるようになったのを思い出しました。これだと前方宣言には使えないですけど。

Anonymous said...

こんな実用性皆無の例を出して、結論に飛躍するのが不自然。

Anonymous said...

質問者です。やはり、最終的に何らかの値としての型以外を戻す関数へ集約する必要がありますよね。
回答ありがとうございました。

Anonymous said...

値としての型、でした。というか関数ポインタ以外を、ですね。失礼しました。

Anonymous said...

全部VCが悪いんですよ。VCが超絶バージョンアップしたら業界のローエンドは飛躍的に上がるんですよ。
だれか、その手腕でMS行ってきてください。
お願いします。

Anonymous said...

関数の戻り値の型にautoってあまり良くないと思いますがね。その関数を使う側からしてみたら明確な型が分からない上に戻り値を変数で受けるのにautoするしか無いわけですよね?
autoは明確な型が分かっている状態で使うのが好ましいと思うのですが如何でしょうか?

Anonymous said...

つまり、最終的にメンバ関数ポインタを返さない何らかの関数に集約する必要があるということだな