ทำไม std :: swap ที่ทำเครื่องหมายว่า constexpr ก่อน C ++ 20 ไม่


14

ใน C ++ 20 std::swapกลายเป็นconstexprฟังก์ชัน

ฉันรู้ว่าห้องสมุดมาตรฐานล้าหลังภาษาในการทำเครื่องหมายสิ่งต่าง ๆconstexprแต่ในปี 2560 <algorithm>เป็นกลุ่มคนที่ค่อนข้างมากเหมือนกับสิ่งอื่น ๆ แต่ - std::swapไม่ใช่ ฉันจำได้ว่ามีข้อบกพร่องบางอย่างเกี่ยวกับภาษาแปลก ๆ ซึ่งป้องกันการทำเครื่องหมายนั้น แต่ฉันลืมรายละเอียด

บางคนสามารถอธิบายสิ่งนี้อย่างกระชับและชัดเจน?

แรงจูงใจ: จำเป็นต้องเข้าใจว่าเหตุใดจึงเป็นความคิดที่ไม่ดีที่จะทำเครื่องหมายstd::swap()ฟังก์ชันเหมือนconstexprในรหัส C ++ 11 / C ++ 14

คำตอบ:


11

ปัญหาภาษาแปลกคือCWG 1581 :

ข้อ 15 [พิเศษ] มีความชัดเจนอย่างสมบูรณ์แบบว่าฟังก์ชั่นสมาชิกพิเศษมีการกำหนดโดยนัยเฉพาะเมื่อพวกเขาถูกใช้งาน สิ่งนี้สร้างปัญหาสำหรับนิพจน์คงที่ในบริบทที่ไม่ได้ประเมินค่า:

struct duration {
  constexpr duration() {}
  constexpr operator int() const { return 0; }
};

// duration d = duration(); // #1
int n = sizeof(short{duration(duration())});

ปัญหาที่นี่คือเราไม่ได้รับอนุญาตให้กำหนดโดยปริยายconstexpr duration::duration(duration&&)ในโปรแกรมนี้ดังนั้นการแสดงออกในรายการ initializer ไม่ใช่การแสดงออกคงที่ (เพราะมันจะเรียกใช้ฟังก์ชั่น constexpr ซึ่งยังไม่ได้กำหนด) ดังนั้น initializer braced มีการแปลงแคบ ดังนั้นโปรแกรมจึงมีรูปแบบไม่ดี

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

คุณสามารถอ่านคำอธิบายปัญหาที่เหลือ

การแก้ไขปัญหานี้ถูกนำมาใช้ในP0859ใน Albuquerque ในปี 2560 (หลังจากจัดส่ง C ++ 17) ปัญหานั้นเป็นตัวบล็อกสำหรับทั้งสองสามารถมีconstexpr std::swap(แก้ไขในP0879 ) และconstexpr std::invoke(แก้ไขในP1065ซึ่งมีตัวอย่าง CWG1581 ด้วย) ทั้งสำหรับ C ++ 20


ตัวอย่างที่เข้าใจง่ายที่สุดในความคิดของฉันคือรหัสจากรายงานข้อผิดพลาด LLVM ที่ระบุใน P1065:

template<typename T>
int f(T x)
{
    return x.get();
}

template<typename T>
constexpr int g(T x)
{
    return x.get();
}

int main() {

  // O.K. The body of `f' is not required.
  decltype(f(0)) a;

  // Seems to instantiate the body of `g'
  // and results in an error.
  decltype(g(0)) b;

  return 0;
}

CWG1581 เป็นข้อมูลเกี่ยวกับเมื่อฟังก์ชันสมาชิก constexpr มีการกำหนดและสร้างความมั่นใจความละเอียดที่พวกเขากำลังที่กำหนดไว้เท่านั้นเมื่อนำมาใช้ หลังจาก P0859 ข้างต้นจะเกิดขึ้นอย่างดี (ประเภทของbคือint)

เนื่องจากstd::swapและstd::invokeทั้งคู่ต้องพึ่งพาการตรวจสอบฟังก์ชั่นสมาชิก (ย้ายการก่อสร้าง / การมอบหมายในอดีตและผู้ดำเนินการโทร / การโทรตัวแทนในภายหลัง) พวกเขาทั้งสองขึ้นอยู่กับการแก้ไขปัญหานี้


ดังนั้นทำไม CWG-1581 ถึงป้องกัน / ทำให้ไม่พึงประสงค์ในการทำเครื่องหมายฟังก์ชั่นการสลับเป็น constexpr
einpoklum

3
@einpoklum แลกเปลี่ยนต้องเป็นstd::is_move_constructible_v<T> && std::is_move_assignable_v<T> trueสิ่งนี้ไม่สามารถเกิดขึ้นได้หากยังไม่มีการสร้างฟังก์ชั่นสมาชิกพิเศษ
NathanOliver

@NathanOliver: เพิ่มสิ่งนี้ในคำตอบของฉัน
einpoklum

5

เหตุผล

(เนื่องจาก @NathanOliver)

ในการอนุญาตให้มีconstexprฟังก์ชั่นการสลับคุณต้องตรวจสอบ - ก่อนที่จะสร้างเทมเพลตสำหรับฟังก์ชั่นนี้ให้เป็นอินสแตนซ์ว่าประเภทที่สลับเปลี่ยนนั้นสามารถเคลื่อนย้ายได้และสามารถกำหนดได้ น่าเสียดายเนื่องจากข้อบกพร่องทางภาษาได้รับการแก้ไขใน C ++ 20 เท่านั้นคุณไม่สามารถตรวจสอบได้เนื่องจากฟังก์ชั่นสมาชิกที่เกี่ยวข้องอาจยังไม่ได้รับการกำหนดตราบใดที่คอมไพเลอร์เกี่ยวข้อง

ลำดับเหตุการณ์

  • 2016: แอนโทนี Polukhin submitts ข้อเสนอP0202เพื่อทำเครื่องหมายทั้งหมดของฟังก์ชั่นเป็น<algorithm>constexpr
  • คณะทำงานหลักของคณะกรรมการมาตรฐานการกล่าวถึงข้อบกพร่องCWG-1581 ปัญหานี้ทำให้เกิดปัญหาในการมีconstexpr std::swap()และconstexpr std::invoke()- ดูคำอธิบายด้านบน
  • 2017: แอนโทนีทบทวนข้อเสนอของเขาสองสามครั้งเพื่อแยกออกstd::swapและสิ่งก่อสร้างอื่น ๆ และได้รับการยอมรับใน C ++ 17
  • 2017: ส่งการแก้ไขปัญหา CWG-1581 เป็นP0859และได้รับการยอมรับจากคณะกรรมการมาตรฐานในปี 2560 (แต่หลังจากส่ง C ++ 17)
  • สิ้นปี 2560: แอนโทนีส่งข้อเสนอเสริมP0879เพื่อสร้างstd::swap()กลุ่ม บริษัท หลังจากการลงมติ CWG-1581
  • 2018: ข้อเสนอเสริมได้รับการยอมรับ (?) เป็น C ++ 20 ในขณะที่ Barry ชี้ให้เห็นก็คือการstd::invoke()แก้ไขconstexpr

กรณีเฉพาะของคุณ

คุณสามารถใช้constexprการแลกเปลี่ยนหากคุณไม่ตรวจสอบความสามารถในการเคลื่อนย้ายและความสามารถในการเคลื่อนย้าย แต่ให้ตรวจสอบคุณสมบัติอื่น ๆ ของประเภทโดยตรงซึ่งจะทำให้แน่ใจได้ว่าโดยเฉพาะ เช่นประเภทดั้งเดิมเท่านั้นและไม่มีคลาสหรือโครงสร้าง หรือตามหลักเหตุผลคุณสามารถละทิ้งการตรวจสอบและจัดการกับข้อผิดพลาดในการคอมไพล์ที่คุณอาจพบและด้วยการสลับพฤติกรรมที่ไม่สม่ำเสมอระหว่างคอมไพเลอร์ ไม่ว่าในกรณีใด ๆ อย่าแทนที่std::swap()ด้วยสิ่งนั้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.