1. ホーム
  2. c++

[解決済み] ラムダに対する関数ポインタとstd::functionの曖昧なオーバーロードを+で解決する(単項のプラス)

2022-11-16 04:03:52

質問

次のコードでは、最初の呼び出しが foo の最初の呼び出しがあいまいであるため、コンパイルに失敗しています。

二つ目は、追加された + が追加されており、関数ポインタのオーバーロードに解決されます。

#include <functional>

void foo(std::function<void()> f) { f(); }
void foo(void (*f)()) { f(); }

int main ()
{
    foo(  [](){} ); // ambiguous
    foo( +[](){} ); // not ambiguous (calls the function pointer overload)
}

は何ですか? + の表記は何をしているのでしょうか?

どのように解決するのですか?

この + という表現で +[](){} は単項 + 演算子です。で次のように定義されています。 [expr.unary.op]/7で定義されています。

単項演算子のオペランドは + 演算子は算術型、スコープされていない列挙型、またはポインタ型でなければならず、結果は引数の値である。

ラムダは算術型等ではありませんが、変換することができます。

[expr.prim.lambda]/3

の型は ラムダ式 [は、ユニークで名前のない非ユニオンクラスの型であり、これは クロージャ型 - と呼ばれるもので、その特性は後述します。

[expr.prim.lambda]/6

<ブロッククオート

のクロージャ型は ラムダ式 を持たない ラムダ捕捉 public 以外の virtualexplicit const への変換機能 関数へのポインタ クロージャ型の関数呼び出し演算子と同じパラメタ及び戻り値の型をもつ関数へのポインタへの変換関数。この変換関数が返す値は,呼び出されたとき,クロージャ型の関数呼び出し演算子を呼び出すのと同じ効果をもつ関数のアドレスでなければならない。

したがって、単項の + は関数ポインタ型への変換を強制し、このラムダに対して void (*)() . したがって、式の型は +[](){} はこの関数ポインタ型 void (*)() .

2つ目のオーバーロード void foo(void (*f)()) はオーバーロードの解決のためのランキングで完全一致となり、したがって(最初のオーバーロードは完全一致ではないので)曖昧さなく選択されます。


ラムダは [](){} は、次のように変換されます。 std::function<void()> の非明示的なテンプレートクラスターによって std::function を満たす任意の型を取ります。 CallableCopyConstructible の要件を満たしている必要があります。

ラムダはまた、次のように変換することができます。 void (*)() の変換機能によって クロージャ型 (の変換機能によって行われます(上記参照)。

どちらもユーザー定義の変換シーケンスで、同じランクのものです。そのため、オーバーロードの解決に失敗するのは 最初 の例では曖昧さのために過負荷の解決が失敗する理由です。


Cassio Neri によると、Daniel Krügler の議論に裏打ちされた、この単項式 + トリックは指定された動作であるべきで、つまりあなたはそれに依存することができます (コメントでの議論を参照してください)。

それでも、曖昧さを回避したいのであれば、関数ポインタ型への明示的なキャストを使用することをお勧めします:SOで何をするのか、なぜそれが機能するのかを尋ねる必要はありません ;)