+ Kullanarak bir lambda için işlev işaretçisi ve std :: function üzerindeki belirsiz aşırı yüklemeyi çözme


94

Aşağıdaki kodda, ilk çağrı foobelirsizdir ve bu nedenle derleme başarısız olur.

+Lambda'dan önce eklenen ikincisi, işlev işaretçisi aşırı yüklemesini çözer.

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

+Gösterim burada ne yapıyor?

Yanıtlar:


100

+İfade +[](){}tekli bir +operatör. [Expr.unary.op] / 7'de aşağıdaki gibi tanımlanır:

Tekli +operatörün işlenen aritmetik, kapsamı açık olmayan numaralandırma veya işaretçi tipine sahip olacaktır ve sonuç, argümanın değeridir.

Lambda aritmetik tipte vb. Değildir, ancak dönüştürülebilir:

[expr.prim.lambda] / 3

Tipi lambda ifade [...], benzersiz, isimsiz kaynaşmama sınıf tipidir - adı kapalı tip -, özellikleri aşağıda tarif edilmiştir.

[expr.prim.lambda] / 6

Bir için kapalı tip lambda ekspresyonu hiçbir ile lambda yakalama bir sahiptir publicolmayan virtualolmayan explicit constdönüşüm fonksiyonu işlevi işaretçi kapak tipinin işlev çağrısı operatörü olarak aynı parametre ve dönüş türleri olan. Bu dönüştürme işlevi tarafından döndürülen değer, çağrıldığında, kapanış türünün işlev çağrısı operatörünü çağırmakla aynı etkiye sahip olan bir işlevin adresi olacaktır.

Bu nedenle, tekli +, dönüşümü bu lambda için olan işlev işaretçi tipine zorlar void (*)(). Bu nedenle, ifadenin +[](){}türü bu işlev işaretçi türüdür void (*)().

İkinci aşırı yük void foo(void (*f)()), aşırı yük çözümü sıralamasında bir Tam Eşleşme olur ve bu nedenle açık bir şekilde seçilir (çünkü ilk aşırı yük bir Tam Eşleşme DEĞİLDİR).


Lambda , ve gereksinimlerini karşılayan herhangi bir türü alan, açık olmayan şablon ctoru aracılığıyla [](){}dönüştürülebilir .std::function<void()>std::functionCallableCopyConstructible

Lambda void (*)(), kapatma tipinin dönüştürme işlevi aracılığıyla da dönüştürülebilir (yukarıya bakın).

Her ikisi de kullanıcı tanımlı dönüşüm dizileridir ve aynı dereceye sahiptir. Bu nedenle ilk örnekte belirsizlik nedeniyle aşırı yük çözümü başarısız oluyor .


Daniel Krügler'in bir argümanıyla desteklenen Cassio Neri'ye göre, bu tekli + numara davranış belirtilmelidir, yani ona güvenebilirsiniz (yorumlardaki tartışmaya bakın).

Yine de, belirsizlikten kaçınmak istiyorsanız, işlev işaretçisi türüne açık bir dönüşüm kullanmanızı tavsiye ederim: SO'ya ne olduğunu ve neden çalıştığını sormanıza gerek yoktur;)


3
@Fred AFAIK üye işlev işaretçileri, ldeğerleri bırakın, üye olmayan işlev işaretçilerine dönüştürülemez. Bir üye işlevi , bir işlev ldeğerine benzer şekilde çağrılabilen std::bindbir std::functionnesneye bağlayabilirsiniz .
dyp

2
@DyP: Zor olanlara güvenebileceğimize inanıyorum. Aslında, bir uygulamanın operator +()durum bilgisiz bir kapatma türüne katkıda bulunduğunu varsayalım . Bu operatörün, kapanış türünün dönüştüğü işleve işaretçi dışında bir şey döndürdüğünü varsayalım. Bu, 5.1.2 / 3'ü ihlal eden bir programın gözlemlenebilir davranışını değiştirir. Lütfen bu gerekçeye katılıyorsanız bana bildirin.
Cassio Neri

2
@CassioNeri Evet, emin olmadığım nokta bu. Bir eklerken gözlemlenebilir davranışın değişebileceğine katılıyorum operator +, ancak bu, başlangıçta hiçbir şeyin olmadığı durumla karşılaştırılıyor operator +. Ancak kapatma tipinde operator +aşırı yük olmayacağı belirtilmemiştir . "Bir uygulama, kapanış tipini aşağıda tarif edilenden farklı bir şekilde tanımlayabilir, ancak bu, programın gözlemlenebilir davranışını [...] dışında değiştirmez" ancak IMO'nun eklenmesi kapanış tipini bundan farklı bir şeye değiştirmez. "aşağıda açıklanmıştır".
Dyp

3
@DyP: Hiçbir operator +()şeyin olmadığı durum , tam olarak standart tarafından tanımlanan durumdur . Standart, bir uygulamanın belirtilenden farklı bir şey yapmasına izin verir. Örneğin eklemek operator +(). Ancak, bu fark bir program tarafından gözlemlenebiliyorsa, o zaman yasa dışıdır. Bir kez comp.lang.c ++ 'da bir kapatma türünün bir typedef ekleyip ekleyemeyeceğini result_typeve diğerinin typedefsonları uyarlanabilir hale getirip getiremeyeceğini sordum (örneğin tarafından std::not1). Bunun gözlemlenebilir olduğu için olamayacağı söylendi. Bağlantıyı bulmaya çalışacağım.
Cassio Neri

6
VS15 size şu eğlenceli hatayı verir: test.cpp (543): hata C2593: 'operatör +' belirsizdir t \ test.cpp (543): not: 'yerleşik C ++ operatörü + (void (__cdecl *) (void )) 't \ test.cpp (543): not: veya' yerleşik C ++ operatörü + (void (__stdcall *) (void)) 't \ test.cpp (543): not: veya' yerleşik C ++ operatörü + (void (__fastcall *) (void)) 't \ test.cpp (543): not: veya' yerleşik C ++ operatörü + (void (__vectorcall *) (void)) 't \ test.cpp (543): not : '(wmain :: <lambda_d983319760d11be517b3d48b95b3fe58>) bağımsız değişken listesi ile eşleşmeye çalışırken test.cpp (543): hata C2088:' + ': sınıf için geçersiz
Ed Lambert
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.