メンバー関数へのポインターを返すメンバー関数へのポインターを返すメンバー関数
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