การแก้ปัญหาโอเวอร์โหลดที่คลุมเครือบนตัวชี้ฟังก์ชันและฟังก์ชัน std :: สำหรับแลมบ์ดาโดยใช้ +


95

ในรหัสต่อไปนี้การเรียกครั้งแรกถึง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)
}

อะไรคือสิ่งที่+สัญกรณ์ทำอะไรที่นี่?

คำตอบ:


101

+ในการแสดงออก+[](){}เป็นเอกภาค+ผู้ประกอบการ มีการกำหนดดังต่อไปนี้ใน [expr.unary.op] / 7:

ตัวถูกดำเนินการของตัวดำเนิน+การยูนารีต้องมีเลขคณิตการแจงนับที่ไม่ได้กำหนดขอบเขตหรือชนิดของตัวชี้และผลลัพธ์คือค่าของอาร์กิวเมนต์

แลมด้าไม่ใช่ประเภทเลขคณิตเป็นต้น แต่สามารถแปลงได้:

[expr.prim.lambda] / 3

ประเภทของlambda-expression [... ] เป็นคลาสที่ไม่ซ้ำกันไม่มีชื่อเรียกว่าประเภทการปิดซึ่งมีการอธิบายคุณสมบัติไว้ด้านล่าง

[expr.prim.lambda] / 6

ประเภทปิดสำหรับแลมบ์ดาแสดงออกที่ไม่มีแลมบ์ดาจับมีpublicไม่ใช่virtualไม่ใช่explicit constฟังก์ชั่นการแปลงตัวชี้ไปยังฟังก์ชั่นที่มีพารามิเตอร์และผลตอบแทนเดียวกันเป็นผู้ประกอบการประเภทฟังก์ชั่นการโทรประเภทปิดของ ค่าที่ส่งคืนโดยฟังก์ชันการแปลงนี้จะเป็นที่อยู่ของฟังก์ชันที่เมื่อเรียกใช้จะมีผลเช่นเดียวกับการเรียกใช้ตัวดำเนินการเรียกฟังก์ชันของประเภทการปิด

ดังนั้นยูนารี+บังคับให้แปลงเป็นชนิดตัวชี้ฟังก์ชันซึ่งเป็นของแลมบ์ดาvoid (*)()นี้ ดังนั้นประเภทของนิพจน์+[](){}จึงเป็นประเภทตัวชี้ฟังก์ชันvoid (*)()นี้

การโอเวอร์โหลดครั้งที่สองvoid foo(void (*f)())จะกลายเป็นการจับคู่แบบตรงทั้งหมดในการจัดอันดับสำหรับการแก้ปัญหาโอเวอร์โหลดดังนั้นจึงถูกเลือกอย่างไม่น่าสงสัย (เนื่องจากการโอเวอร์โหลดครั้งแรกไม่ใช่การจับคู่แบบตรงทั้งหมด)


แลมบ์ดา[](){}สามารถแปลงเป็นstd::function<void()>ผ่าน ctor เทมเพลตที่ไม่ชัดเจนstd::functionซึ่งใช้ประเภทใดก็ได้ที่ตรงตามข้อกำหนดCallableและCopyConstructibleข้อกำหนด

แลมบ์ดายังสามารถแปลงvoid (*)()เป็นฟังก์ชั่นการแปลงของประเภทการปิด (ดูด้านบน)

ทั้งสองเป็นลำดับ Conversion ที่ผู้ใช้กำหนดเองและมีอันดับเดียวกัน นั่นเป็นสาเหตุที่ความละเอียดเกินพิกัดล้มเหลวในตัวอย่างแรกเนื่องจากความไม่ชัดเจน


ตามที่ Cassio Neri ได้รับการสนับสนุนจากการโต้แย้งโดย Daniel Krügler +เคล็ดลับที่เป็นเอกภาพนี้ควรระบุพฤติกรรมกล่าวคือคุณสามารถพึ่งพาได้ (ดูการอภิปรายในความคิดเห็น)

อย่างไรก็ตามฉันขอแนะนำให้ใช้การแคสต์อย่างชัดเจนกับประเภทตัวชี้ฟังก์ชันหากคุณต้องการหลีกเลี่ยงความคลุมเครือ: คุณไม่จำเป็นต้องถามว่า SO คืออะไรและทำไมถึงใช้งานได้;)


3
ตัวชี้ฟังก์ชันสมาชิก @Fred AFAIK ไม่สามารถแปลงเป็นพอยน์เตอร์ฟังก์ชันที่ไม่ใช่สมาชิกได้นับประสาฟังก์ชัน lvalues คุณสามารถผูกฟังก์ชันสมาชิกผ่านstd::bindไปยังstd::functionวัตถุที่สามารถเรียกคล้ายกับ lvalue ฟังก์ชั่น
dyp

2
@DyP: ฉันเชื่อว่าเราสามารถพึ่งพาหากินได้ อันที่จริงสมมติว่าการดำเนินการเพิ่มoperator +()ประเภทการปิดแบบไร้สัญชาติ สมมติว่าตัวดำเนินการนี้ส่งคืนสิ่งอื่นที่ไม่ใช่ตัวชี้เพื่อทำงานที่ชนิดการปิดแปลงเป็น จากนั้นสิ่งนี้จะเปลี่ยนพฤติกรรมที่สังเกตได้ของโปรแกรมที่ละเมิด 5.1.2 / 3 โปรดแจ้งให้เราทราบหากคุณเห็นด้วยกับเหตุผลนี้
Cassio Neri

2
@CassioNeri ใช่จุดที่ฉันไม่แน่ใจ ฉันยอมรับว่าพฤติกรรมที่สังเกตได้อาจเปลี่ยนแปลงได้เมื่อเพิ่ม an operator +แต่นี่เป็นการเปรียบเทียบกับสถานการณ์ที่ไม่มีจุดoperator +เริ่มต้นด้วย แต่ไม่ได้ระบุว่าประเภทการปิดจะต้องไม่มีการoperator +โอเวอร์โหลด "การใช้งานอาจกำหนดประเภทการปิดแตกต่างไปจากที่อธิบายไว้ด้านล่างหากสิ่งนี้ไม่ได้เปลี่ยนแปลงพฤติกรรมที่สังเกตได้ของโปรแกรมนอกเหนือจาก [... ]" แต่ IMO ที่เพิ่มตัวดำเนินการจะไม่เปลี่ยนประเภทการปิดเป็นสิ่งที่แตกต่างจาก "อธิบายไว้ด้านล่าง"
dyp

3
@DyP: สถานการณ์ที่ไม่มีoperator +()สิ่งที่อธิบายโดยมาตรฐาน มาตรฐานอนุญาตให้การนำไปใช้ทำสิ่งที่แตกต่างจากที่ระบุไว้ operator +()ยกตัวอย่างเช่นการเพิ่ม อย่างไรก็ตามหากความแตกต่างนี้สามารถสังเกตได้โดยโปรแกรมแสดงว่าผิดกฎหมาย เมื่อฉันถามใน comp.lang.c ++ ตรวจสอบว่าประเภทการปิดสามารถเพิ่ม typedef สำหรับresult_typeและอื่น ๆ ที่typedefsจำเป็นเพื่อให้ปรับเปลี่ยนได้ (เช่นโดยstd::not1) ฉันบอกว่ามันไม่สามารถเพราะนี่เป็นสิ่งที่สังเกตได้ เดี๋ยวจะลองหาลิงค์
Cassio Neri

6
VS15 ให้ข้อผิดพลาดที่น่าสนุกนี้: test.cpp (543): ข้อผิดพลาด C2593: 'operator +' ไม่ชัดเจน t \ test.cpp (543): note: อาจเป็น 'ตัวดำเนินการ C ++ ในตัว + (โมฆะ (__cdecl *) (โมฆะ )) 't \ test.cpp (543): หมายเหตุ: หรือ' ตัวดำเนินการ C ++ ในตัว + (โมฆะ (__stdcall *) (โมฆะ)) 't \ test.cpp (543): หมายเหตุ: หรือ' ตัวดำเนินการ C ++ ในตัว + (โมฆะ (__fastcall *) (โมฆะ)) 't \ test.cpp (543): หมายเหตุ: หรือ' ตัวดำเนินการ C ++ ในตัว + (โมฆะ (__vectorcall *) (โมฆะ)) 't \ test.cpp (543): หมายเหตุ : ในขณะที่พยายามจับคู่รายการอาร์กิวเมนต์ '(wmain :: <lambda_d983319760d11be517b3d48b95b3fe58>) test.cpp (543): ข้อผิดพลาด C2088:' + ': ผิดกฎหมายสำหรับคลาส
Ed Lambert
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.