C++における関数呼び出しという性質を抽象化しまとめた、仮想操作 INVOKE を定義する。
- C++17からは、仮想操作 INVOKE を実体化した
std::invoke関数テンプレートが提供される。 - C++23からは、仮想操作 INVOKE
<R>を実体化したstd::invoke_r関数テンプレートが提供される。
用語定義
- call-signature とは、戻り値型に続けて丸カッコの中に0個以上の引数型を並べたものである。 cf.
int ( std::string, int ) - callable-type とは、関数呼び出し演算子を適用できる型 ( 関数、関数への参照、関数へのポインタ、
operator ()をオーバーロードした型もしくはそれを(直接または間接的に)public継承した型 ) もしくはメンバへのポインタ型を指す。 - callable-object は、 callable-type 型のオブジェクトである。
- call-wrapper-type は、 callable-object を保持し、自身に対する関数呼び出し操作が行われたとき、保持しているオブジェクトに委譲する。
- call-wrapper は、 call-wrapper-type 型のオブジェクトである。
- target-object とは、 callable-object に保持されているオブジェクトのことである。
要件(C++14まで)
- 仮想操作 INVOKE
(f, t1, t2, ..., tN)を次のように定義する。fが型Tのメンバ関数へのポインタであり、t1が T 型のオブジェクトあるいはTまたはTを継承した型への参照であるとき、(t1.*f)(t2, ..., tN)と同じ効果を持つ。fが型Tのメンバ関数へのポインタであり、t1が上記の条件に当てはまらない場合、((*t1).*f)(t2, ..., tN)と同じ効果を持つ。N == 1で、fが型Tのメンバオブジェクトへのポインタであり、t1がT型のオブジェクトあるいはTまたはTを継承した型への参照であるとき、t1.*fと同じ効果を持つ。N == 1で、fが型Tのメンバオブジェクトへのポインタであり、t1が上記の条件に当てはまらない場合、(*t1).*fと同じ効果を持つ。- 上記の条件のどれにも当てはまらない場合、
f(t1, t2, ..., tN)と同じ効果を持つ。
- INVOKE
(f, t1, t2, ..., tN, R)を、 INVOKE(f, t1, t2, ..., tN)の実行結果の戻り値が型Rに暗黙的に変換されること、と定義する。 - call-wrapper が weak-result-type を用意している場合、メンバ型
result_typeはtarget-object の型Tに応じて次のように定義される。 - すべての call-wrapper は、MoveAssignable でなければならない。
要件(C++17)
- 仮想操作 INVOKE
(f, t1, t2, ..., tN)を次のように定義する。fが型Tのメンバ関数へのポインタであり、is_base_of_v<T, decay_t<decltype(t1)>> == true(t1がTまたはTを継承した型のオブジェクト/参照)であるとき、(t1.*f)(t2, ..., tN)と同じ効果を持つ。fが型Tのメンバ関数へのポインタであり、decay_t<decltype(t1)>がreference_wrapper<T>(t1がreference_wrapperの特殊化)であるとき、(t1.get().*f)(t2, ..., tN)と同じ効果を持つ。fが型Tのメンバ関数へのポインタであり、t1が上記の条件に当てはまらない場合(例えば、t1がTのポインタ)、((*t1).*f)(t2, ..., tN)と同じ効果を持つ。N == 1で、fが型Tのメンバオブジェクトへのポインタであり、is_base_of_v<T, decay_t<decltype(t1)>> == true(t1がTまたはTを継承した型のオブジェクト/参照)であるとき、t1.*fと同じ効果を持つ。N == 1で、fが型Tのメンバオブジェクトへのポインタであり、decay_t<decltype(t1)>がreference_wrapper<T>(t1がreference_wrapperの特殊化)であるとき、t1.get().*fと同じ効果を持つ。N == 1で、fが型Tのメンバオブジェクトへのポインタであり、t1が上記の条件に当てはまらない場合(例えば、t1がTのポインタ)、(*t1).*fと同じ効果を持つ。- 上記の条件のどれにも当てはまらない場合、
f(t1, t2, ..., tN)と同じ効果を持つ。
- INVOKE
<R>(f, t1, t2, ..., tN)を次のように定義する。Rがvoidかそのcv修飾の場合は、static_cast<void>(INVOKE(f, t1, t2, ..., tN))。- それ以外の場合は、INVOKE
(f, t1, t2, ..., tN)の実行結果の戻り値が型Rに暗黙的に変換されること。
- すべての call-wrapper は、MoveConstructible でなければならない。
要件(C++20)
- 仮想操作 INVOKE
(f, t1, t2, ..., tN)を次のように定義する。fが型Tのメンバ関数へのポインタであり、is_base_of_v<T, remove_cvref_t<decltype(t1)>> == true(t1がTまたはTを継承した型のオブジェクト/参照)であるとき、(t1.*f)(t2, ..., tN)と同じ効果を持つ。fが型Tのメンバ関数へのポインタであり、remove_cvref_t<decltype(t1)>がreference_wrapper<T>(t1がreference_wrapperの特殊化)であるとき、(t1.get().*f)(t2, ..., tN)と同じ効果を持つ。fが型Tのメンバ関数へのポインタであり、t1が上記の条件に当てはまらない場合(例えば、t1がTのポインタ)、((*t1).*f)(t2, ..., tN)と同じ効果を持つ。N == 1で、fが型Tのメンバオブジェクトへのポインタであり、is_base_of_v<T, remove_cvref_t<decltype(t1)>> == true(t1がTまたはTを継承した型のオブジェクト/参照)であるとき、t1.*fと同じ効果を持つ。N == 1で、fが型Tのメンバオブジェクトへのポインタであり、remove_cvref_t<decltype(t1)>がreference_wrapper<T>(t1がreference_wrapperの特殊化)であるとき、t1.get().*fと同じ効果を持つ。N == 1で、fが型Tのメンバオブジェクトへのポインタであり、t1が上記の条件に当てはまらない場合(例えば、t1がTのポインタ)、(*t1).*fと同じ効果を持つ。- 上記の条件のどれにも当てはまらない場合、
f(t1, t2, ..., tN)と同じ効果を持つ。
- INVOKE
<R>(f, t1, t2, ..., tN)を次のように定義する。Rがvoidかそのcv修飾の場合は、static_cast<void>(INVOKE(f, t1, t2, ..., tN))。- それ以外の場合は、INVOKE
(f, t1, t2, ..., tN)の実行結果の戻り値が型Rに暗黙的に変換されること。
- すべての call-wrapper は、Cpp17MoveConstructible かつ Cpp17Destructible でなければならない。
要件(C++23差分)
C++20 における 2. について、次の文言を項目の最後に追加する。この変更は、Rが参照かつINVOKEの実行結果がRに束縛されることで寿命が延長される場合にダングリング参照が作成されてしまう事例を検出するための要件である。
reference_converts_from_temporary_v<R, decltype(INVOKE(f, t1, t2, …, tN))> == trueの場合、INVOKE<R>(f, t1, t2, …, tN)は不適格。
まとめ
第1引数がメンバ関数へのポインタの場合でも非静的メンバデータへのポインタの場合でも,第2引数がクラスオブジェクトへの参照の場合でもポインタの場合でもポインタっぽいものの場合でも,なんか知らんけどそれっぽく上手くいく ように取り計らった操作のことである。
関連項目
参照
- P0777R1 Treating Unnecessary
decay- C++20から
decay_tをremove_cvref_tへ変更。
- C++20から
- P2136R3
invoke_r