+を使用してラムダの関数ポインタとstd :: functionのあいまいなオーバーロードを解決する


93

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

2番目は+、ラムダの前に追加され、関数ポインターオーバーロードに解決されます。

#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)
}

+ここでの表記は何ですか?

回答:


98

+式には+[](){}単項である+オペレータ。[expr.unary.op] / 7では次のように定義されています。

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

ラムダは算術型などではありませんが、変換できます。

[expr.prim.lambda] / 3

ラムダ式 [...] の型は、名前が付けられていない一意のunionクラス型(クロージャー型と呼ばれます)であり、そのプロパティについて以下で説明します。

[expr.prim.lambda] / 6

以下のための閉鎖型ラムダ式無有するラムダキャプチャ有するpublicvirtual以外explicit constの変換関数関数へのポインタを閉鎖型の関数呼び出し演算子と同じパラメータと戻り型を有します。この変換関数によって返される値は、呼び出されたときに、クロージャタイプの関数呼び出し演算子を呼び出すのと同じ効果を持つ関数のアドレスになります。

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

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


ラムダ[](){}std::function<void()>、の非明示的なテンプレートctor を介してに変換できます。これstd::functionCallableCopyConstructible要件を満たすすべての型を取ります。

ラムダvoid (*)()は、クロージャタイプの変換関数を介して(上記を参照)に変換することもできます。

どちらもユーザー定義の変換シーケンスであり、同じランクです。そのため、最初の例ではあいまいさのために過負荷の解決が失敗します。


DanielKrüglerの議論に裏打ちされたCassio Neriによると、この単項+トリックは動作を指定する必要があります。つまり、これに依存することができます(コメントの説明を参照)。

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


3
@Fred AFAIKメンバー関数ポインターは、関数の左辺値はもちろん、非メンバー関数ポインターに変換できません。あなたは経由してメンバ関数をバインドすることができますstd::bindするstd::function機能左辺値と同様に呼び出すことができるオブジェクト。
dyp 2013

2
@DyP:私たちはトリッキーに頼ることができると信じています。実際、operator +()ステートレスなクロージャタイプに実装が追加するとします。この演算子は、クロージャタイプが変換する関数へのポインタ以外のものを返すと仮定します。次に、これは5.1.2 / 3に違反するプログラムの観察可能な動作を変更します。この推論に同意する場合はお知らせください。
Cassio Neri 2013

2
@CassioNeriはい、それは私がわからない点です。を追加するとoperator +、監視可能な動作が変わる可能性があることに同意しますが、これはoperator +、最初から存在しない状況と比較しています。ただし、クロージャタイプがoperator +オーバーロードを持たないことは指定されていません。「実装は、[...]以外でプログラムの監視可能な動作が変更されない限り、以下で説明されているものとは異なるクロージャタイプを定義する可能性があります」しかし、IMOが演算子を追加しても、クロージャタイプは何と異なるものに変更されません「以下に説明」です。
dyp 2013

3
@DyP:operator +()まったくない状況は、標準で記述されている状況とまったく同じです。標準により、実装は指定されたものとは異なる何かを行うことができます。たとえば、を追加しoperator +()ます。ただし、この違いがプログラムで観察できる場合は、違法です。いったんcomp.lang.c ++。moderatedで、クロージャタイプがtypedefを追加できるかどうか、result_typeおよびtypedefsそれらを(たとえばによってstd::not1)適応可能にするために必要なタイプを追加できるかどうかを尋ねました。これは観測可能だったのでできないと言われました。リンクを探してみます。
Cassio Neri 2013

6
VS15はこの楽しいエラーを提供します:test.cpp(543):エラーC2593: '演算子+'があいまいですt \ test.cpp(543):注: '組み込みのC ++演算子+(void(__cdecl *)(void )) 't \ test.cpp(543):注:または'ビルトインC ++演算子+(void(__stdcall *)(void)) 't \ test.cpp(543):注:または'ビルトインC ++演算子+ (void(__fastcall *)(void)) 't \ test.cpp(543):注:または'組み込みC ++演算子+(void(__vectorcall *)(void)) 't \ test.cpp(543):注:引数リストを一致させようとしているとき '(wmain :: <lambda_d983319760d11be517b3d48b95b3fe58>)test.cpp(543):エラーC2088:' + ':クラスには不正
Ed Lambert
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.