C ++ 20 การกำหนดนอกคลาสในเทมเพลตคลาส


12

จนถึงมาตรฐาน C ++ 20 ของ C ++ เมื่อเราต้องการกำหนดโอเปอเรเตอร์นอกคลาสซึ่งใช้สมาชิกส่วนตัวของคลาสเทมเพลตเราจะใช้โครงสร้างที่คล้ายกับสิ่งนี้:

template <typename T>
class Foo;

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs);

template <typename T>
class Foo {
public:
    constexpr Foo(T k) : mK(k) {}

    constexpr friend bool operator==<T>(T lhs, const Foo& rhs);

private:
    T mK;
};

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs) {
    return lhs == rhs.mK;
}

int main() {
    return 1 == Foo<int>(1) ? 0 : 1;
}

อย่างไรก็ตามตั้งแต่ C ++ 20 เราสามารถละเว้นการประกาศนอกคลาสดังนั้นจึงมีการประกาศล่วงหน้าดังนั้นเราจึงสามารถออกไปได้ด้วย:

template <typename T>
class Foo {
public:
    constexpr Foo(T k) : mK(k) {}

    constexpr friend bool operator==<T>(T lhs, const Foo& rhs);
private:
    T mK;
};

template <typename T>
constexpr bool operator==(T lhs, const Foo<T>& rhs) {
    return lhs == rhs.mK;
}

Demo

ตอนนี้คำถามของฉันคือส่วนใดของ C ++ 20 ที่อนุญาตให้เราทำเช่นนั้น? และทำไมจึงไม่สามารถทำได้ในมาตรฐาน C ++ ก่อนหน้านี้


ในขณะที่มันถูกชี้ให้เห็นในความคิดเห็นเสียงดังกราวไม่ยอมรับรหัสนี้ที่นำเสนอในการสาธิตซึ่งแสดงให้เห็นว่านี่อาจเป็นข้อผิดพลาดใน gcc

ฉันได้ยื่นรายงานข้อผิดพลาดใน Bugzilla ของ gcc


2
ฉันชอบในการกำหนดชั้นเรียนเป็นการส่วนตัวหลีกเลี่ยงฟังก์ชั่นเทมเพลต (และการหัก "ปัญหา" (ไม่ตรงกัน"c string" == Foo<std::string>("foo")))
Jarod42

@ Jarod42 ฉันเห็นด้วยอย่างยิ่งว่าฉันชอบคำนิยามในชั้นเรียนด้วย ฉันเพิ่งประหลาดใจที่พบว่า C ++ 20 ไม่อนุญาตให้เราทำซ้ำฟังก์ชันลายเซ็นสามครั้งเมื่อกำหนดเป็นคลาสซึ่งอาจเป็นประโยชน์ใน API สาธารณะที่มีการนำไปใช้ในไฟล์. inl ที่ซ่อนอยู่
ProXicT

ฉันไม่ได้สังเกตว่ามันเป็นไปไม่ได้ ทำไมฉันถึงใช้มันมาโดยไม่มีปัญหา
ALX23z

1
อืมในอุณหภูมิไม่เปลี่ยนแปลงมากนักโดยเฉพาะอย่างยิ่งไม่ใช่1.3ซึ่งควรรับผิดชอบต่อพฤติกรรมนี้ เนื่องจากเสียงดังกราวไม่ยอมรับรหัสของคุณฉันจึงโน้มตัวไปยัง gcc ที่มีปัญหา
n314159

@ ALX23z มันทำงานโดยไม่มีการประกาศนอกชั้นเรียนถ้าชั้นไม่ได้ templated
ProXicT

คำตอบ:


2

GCC มีข้อบกพร่อง

การค้นหาชื่อจะดำเนินการเสมอสำหรับชื่อเทมเพลตที่ปรากฏก่อน <แม้ว่าชื่อที่เป็นปัญหาจะเป็นชื่อที่ถูกประกาศในการประกาศ (เพื่อนผู้เชี่ยวชาญเฉพาะทางหรือการสร้างอินสแตนซ์อย่างชัดเจน)

เนื่องจากชื่อoperator==ในการประกาศของเพื่อนเป็นชื่อที่ไม่มีเงื่อนไขและอยู่ภายใต้การค้นหาชื่อในเทมเพลตจึงใช้กฎการค้นหาชื่อสองเฟส ในบริบทoperator==นี้ไม่ใช่ชื่อที่ขึ้นต่อกัน (ไม่ใช่ส่วนหนึ่งของการเรียกฟังก์ชันดังนั้น ADL จึงไม่นำไปใช้) ดังนั้นชื่อจะถูกค้นหาและผูกไว้กับจุดที่ปรากฏ (ดู [temp.nondep] วรรค 1) operator==ตัวอย่างของคุณจะไม่ดีที่เกิดขึ้นเนื่องจากการค้นหาชื่อนี้พบว่าการประกาศของไม่มี

ฉันคาดว่า GCC จะยอมรับสิ่งนี้ในโหมด C ++ 20 เนื่องจากP0846R0ซึ่งอนุญาตให้ใช้ (ตัวอย่าง) operator==<T>(a, b)ในเทมเพลตแม้ว่าจะไม่มีการประกาศล่วงหน้าoperator==ว่าเป็นเทมเพลตก็ตาม

นี่คือตัวอย่างทดสอบที่น่าสนใจยิ่งขึ้น:

template <typename T> struct Foo;

#ifdef WRONG_DECL
template <typename T> bool operator==(Foo<T> lhs, int); // #1
#endif

template <typename T> struct Foo {
  friend bool operator==<T>(Foo<T> lhs, float); // #2
};

template <typename T> bool operator==(Foo<T> lhs, float); // #3
Foo<int> f;

กับ -DWRONG_DECL , GCC และเสียงดังกราวยอมรับว่าโปรแกรมนี้เป็นโปรแกรมที่ไม่ดีเกิดขึ้น: การค้นหาอย่างไม่มีเงื่อนไขสำหรับเพื่อนประกาศ # 2 ในบริบทของการกำหนดแม่แบบพบประกาศ # 1 ซึ่งไม่ตรงกับเพื่อน instantiated Foo<int>ของ การประกาศ # 3 ไม่ได้รับการพิจารณาเนื่องจากการค้นหาที่ไม่มีเงื่อนไขในเทมเพลตไม่พบ

กับ -UWRONG_DECL GCC (ใน C ++ 17 และก่อนหน้านี้) และ Clang ยอมรับว่าโปรแกรมนี้มีรูปแบบไม่ดีด้วยเหตุผลที่แตกต่าง: การค้นหาแบบไม่มีเงื่อนไขสำหรับoperator==บรรทัด # 2 ไม่พบสิ่งใดเลย

แต่ด้วย-UWRONG_DECLGCC ในโหมด C ++ 20 ดูเหมือนจะตัดสินใจว่าไม่เป็นไรที่การค้นหาอย่างไม่มีเงื่อนไขสำหรับoperator==# 2 ล้มเหลว (น่าจะเป็นเพราะ P0846R0) จากนั้นปรากฏขึ้นเพื่อทำการค้นหาซ้ำจากบริบทการสร้างแม่แบบตอนนี้ค้นหา # 3 ใน การละเมิดกฎการค้นหาชื่อสองเฟสปกติสำหรับเทมเพลต


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