関数への参照と関数ポインタ、func と &func
機能を引数にとりたいとき複数の方法があり、
関数ポインタ、 std::function
、などは何度もつかってきたものの、「関数への参照」に出会って少しとまどったので記録します
関数への参照
int func(double x){ return x; }
と定義されているとき、func
の型は int(double)
です。これを引き回したいとき
int(double) hoge = func; // error
のようにコピーすることはできませんが、ポインタまたは参照をとることができます:
int (&ref)(double) = func; // ok int (*fptr)(double) = &func; // ok int (*fptr2)(double) = func; // also ok
このとき、ref
の型は int(&)(double)
、 fptr
とfptr2
の型は int(*)(double)
です:
std::cout << std::boolalpha; std::cout << std::is_same_v<decltype(ref), int(&)(double)> << std::endl; // -> true std::cout << std::is_same_v<decltype(fptr), int(*)(double)> << std::endl; // -> true
関数を引数に渡したときのテンプレート型推論
関数ポインタの代入にあたって
int (*fptr)(double) = &func; // ok int (*fptr2)(double) = func; // also ok
の 2 種類の書き方が許容されるので、「&
関数名」と「関数名」を混同して両方関数ポインタを表すものと思い込んでいましたが、2行めの書き方は関数型*1 int(double)
から関数ポインタ型 int(*)(double)
への暗黙の変換が行われている*2だけでした。
たまたま現代のコンピュータで「関数」がプログラム上のアドレスに JUMP することなので、 &
でアドレス取得をしても、 *
でデリファレンスしても値が変わらない ということのようです。
テンプレート型推論を絡めると、(func)
と (&func)
の違いをあらわにすることはできます。
template<typename Callable> void check(Callable&& callable){ if(std::is_same<decltype(callable), void(&)()>::value){ std::cout << "void(&)()" << std::endl; } if(std::is_same<decltype(callable), void(*&&)()>::value){ std::cout << "void(*&&)()" << std::endl; } } void func(){ } int main() { check(func); // -> void(&)() check(&func); // -> void(*&&)() }
func
は lvalue なのでユニバーサル参照 Callable&&
は func
への参照型となる void(&)()
に推論され、
&func
はアドレス取得演算子 &
の結果として rvalue であり、 Callable&&
はその rvalue への右辺値参照 void(* &&)()
になります。
関数への参照に関する GCC のバグ
7.2.0 までの GCC で、関数への参照 T(&)(...)
をコピーキャプチャするラムダがコンパイルできません。
8.0.1 では修正されているようです。
template<typename Callable> auto enlambda(Callable&& callable){ return [=](){ callable(); }; } void f(){ } auto lambda = enlambda(f);
- GCC 6.3.0 NG
- GCC 8.0.1 OK
- clang 4.0.1 OK
- Visual C++ 2017 (VC++14.10) OK