TL; DR
ก่อนที่คุณจะพยายามอ่านโพสต์นี้ให้รู้ว่า:
- พบวิธีแก้ไขปัญหาที่นำเสนอด้วยตัวเองแต่ฉันก็ยังอยากรู้ว่าการวิเคราะห์นั้นถูกต้องหรือไม่
- ฉันได้จัดทำแพคเกจลงใน
fameta::counter
ชั้นเรียนที่สามารถแก้ไขนิสัยใจคอที่เหลืออยู่สองสามข้อ คุณสามารถค้นหาได้ใน GitHub ; - คุณสามารถเห็นมันในการทำงานใน godbolt
มันเริ่มต้นอย่างไร
ตั้งแต่ Filip Roséenค้นพบ / คิดค้นในปี 2015 เวทมนตร์ดำที่รวบรวมเวลาอยู่ใน C ++ฉันได้รับการหมกมุ่นกับอุปกรณ์อย่างอ่อนโยนดังนั้นเมื่อ CWG ตัดสินใจว่าการทำงานต้องไปฉันผิดหวัง แต่ก็ยังหวังว่าใจของพวกเขา อาจมีการเปลี่ยนแปลงโดยแสดงกรณีการใช้งานที่น่าสนใจ
จากนั้นสองสามปีที่ผ่านมาฉันตัดสินใจที่จะดูสิ่งนั้นอีกครั้งเพื่อให้uberswitch esสามารถซ้อนกันได้ - กรณีใช้ที่น่าสนใจในความคิดของฉัน - เพียงเพื่อค้นพบว่ามันจะไม่ทำงานอีกต่อไปกับเวอร์ชันใหม่ของ คอมไพเลอร์ที่มีอยู่แม้ว่าปัญหา 2118คือ (และยังคงเป็น ) ในสถานะเปิด: โค้ดจะคอมไพล์ แต่ตัวนับจะไม่เพิ่มขึ้น
มีการรายงานปัญหาบนเว็บไซต์ของRoséenและเมื่อเร็ว ๆ นี้บน stackoverflow: C ++ รองรับเคาน์เตอร์เวลาคอมไพล์หรือไม่?
ไม่กี่วันที่ผ่านมาฉันตัดสินใจลองแก้ไขปัญหาอีกครั้ง
ฉันต้องการที่จะเข้าใจสิ่งที่มีการเปลี่ยนแปลงในคอมไพเลอร์ที่ทำให้ C ++ ที่ดูเหมือนจะยังคงใช้งานได้ไม่ทำงานอีกต่อไป ด้วยเหตุนี้ฉันจึงค้นหาผู้คนให้พูดคุยเกี่ยวกับเรื่องนี้อย่างกว้างขวางและห่างไกล แต่ก็ไม่มีประโยชน์ ดังนั้นฉันจึงเริ่มทดลองและมาถึงข้อสรุปบางอย่างที่ฉันนำเสนอที่นี่หวังว่าจะได้รับข้อเสนอแนะจากความรู้ที่ดีกว่าตัวเองที่นี่
ด้านล่างฉันกำลังนำเสนอรหัสดั้งเดิมของRoséenเพื่อความชัดเจน สำหรับคำอธิบายวิธีการใช้งานโปรดดูที่เว็บไซต์ของเขา :
template<int N>
struct flag {
friend constexpr int adl_flag (flag<N>);
};
template<int N>
struct writer {
friend constexpr int adl_flag (flag<N>) {
return N;
}
static constexpr int value = N;
};
template<int N, int = adl_flag (flag<N> {})>
int constexpr reader (int, flag<N>) {
return N;
}
template<int N>
int constexpr reader (float, flag<N>, int R = reader (0, flag<N-1> {})) {
return R;
}
int constexpr reader (float, flag<0>) {
return 0;
}
template<int N = 1>
int constexpr next (int R = writer<reader (0, flag<32> {}) + N>::value) {
return R;
}
int main () {
constexpr int a = next ();
constexpr int b = next ();
constexpr int c = next ();
static_assert (a == 1 && b == a+1 && c == b+1, "try again");
}
ด้วยคอมไพเลอร์ g ++ และ clang ++ ล่าสุด-ish ให้next()
ผลตอบแทนที่ 1 เสมอเมื่อทำการทดลองเล็กน้อยปัญหาอย่างน้อยกับ g ++ น่าจะเป็นที่เมื่อคอมไพเลอร์ประเมินค่าเริ่มต้นของฟังก์ชันเท็มเพลตพารามิเตอร์ในครั้งแรกที่เรียกฟังก์ชัน ฟังก์ชั่นเหล่านั้นไม่ได้ทำให้เกิดการประเมินค่าพารามิเตอร์เริ่มต้นอีกครั้งดังนั้นจึงไม่เคยทำให้ฟังก์ชั่นใหม่ ๆ เกิดขึ้นทันที
คำถามแรก
- คุณเห็นด้วยกับการวินิจฉัยของฉันนี้จริง ๆ ?
- ถ้าใช่พฤติกรรมใหม่นี้ได้รับคำสั่งจากมาตรฐานหรือไม่? ก่อนหน้านี้เป็นข้อผิดพลาดหรือไม่?
- ถ้าไม่เช่นนั้นปัญหาคืออะไร
เมื่อคำนึงถึงข้างต้นแล้วฉันก็คิดหาวิธีทำ: ทำเครื่องหมายการnext()
ขอร้องแต่ละอันด้วยรหัสซ้ำที่เพิ่มขึ้นซ้ำซากจำเจเพื่อส่งต่อไปยัง callees เพื่อไม่ให้มีการโทรเหมือนกันดังนั้นบังคับให้คอมไพเลอร์ประเมินค่าอาร์กิวเมนต์ทั้งหมด แต่ละครั้ง.
ดูเหมือนว่าเป็นภาระที่จะทำเช่นนั้น แต่การคิดว่ามันสามารถใช้มาโครมาตรฐาน ( __LINE__
หรือ__COUNTER__
ที่ใดก็ได้) ที่ซ่อนอยู่ในcounter_next()
แมโครที่เหมือนฟังก์ชั่น
ดังนั้นฉันจึงคิดสิ่งต่อไปนี้ซึ่งฉันนำเสนอในรูปแบบที่ง่ายที่สุดที่แสดงปัญหาที่ฉันจะพูดถึงในภายหลัง
template <int N>
struct slot;
template <int N>
struct slot {
friend constexpr auto counter(slot<N>);
};
template <>
struct slot<0> {
friend constexpr auto counter(slot<0>) {
return 0;
}
};
template <int N, int I>
struct writer {
friend constexpr auto counter(slot<N>) {
return I;
}
static constexpr int value = I-1;
};
template <int N, typename = decltype(counter(slot<N>()))>
constexpr int reader(int, slot<N>, int R = counter(slot<N>())) {
return R;
};
template <int N>
constexpr int reader(float, slot<N>, int R = reader(0, slot<N-1>())) {
return R;
};
template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N>())+1>::value) {
return R;
}
int a = next<11>();
int b = next<34>();
int c = next<57>();
int d = next<80>();
คุณสามารถสังเกตผลลัพธ์ของข้างบนในgodboltซึ่งฉันจับภาพหน้าจอสำหรับ lazies
และอย่างที่คุณเห็นด้วย trunk g ++ และ clang ++ จนถึง 7.0.0 มันใช้งานได้! ตัวนับเพิ่มจาก 0 เป็น 3 ตามที่คาดไว้ แต่ด้วยเวอร์ชันเสียงดังกราว ++ สูงกว่า 7.0.0 ไม่เป็นเช่นนั้น
หากต้องการเพิ่มการดูถูกการบาดเจ็บฉันได้พยายามทำให้เสียงดังกราว ++ เป็นเวอร์ชัน 7.0.0 พังโดยเพียงแค่เพิ่มพารามิเตอร์ "บริบท" ในการผสมเช่นที่เคาน์เตอร์จะผูกกับบริบทนั้นและเช่นสามารถ จะเริ่มต้นใหม่เมื่อใดก็ตามที่มีการกำหนดบริบทใหม่ซึ่งเปิดขึ้นเพื่อความเป็นไปได้ที่จะใช้จำนวนที่ไม่มีที่สิ้นสุดของเคาน์เตอร์ ด้วยตัวแปรนี้เสียงดังกราว ++ เหนือรุ่น 7.0.0 ไม่ผิดพลาด แต่ก็ยังไม่ได้ผลตามที่คาดหวัง อยู่บน godbolt
ที่สูญเสียของเบาะแสใด ๆ เกี่ยวกับสิ่งที่เกิดขึ้นผมได้ค้นพบcppinsights.ioเว็บไซต์หนึ่งที่ช่วยให้เห็นวิธีการและเมื่อแม่ได้รับ instantiated การใช้บริการนั้นสิ่งที่ฉันคิดว่าเกิดขึ้นคือเสียงดังกังวาน ++ ไม่ได้กำหนดfriend constexpr auto counter(slot<N>)
ฟังก์ชั่นใด ๆ เมื่อใดก็ตามที่writer<N, I>
มีอินสแตนซ์
ความพยายามที่จะเรียกcounter(slot<N>)
หา N ใด ๆ ที่ควรได้รับอินสแตนซ์แล้วน่าจะเป็นพื้นฐานของสมมติฐานนี้
แต่ถ้าฉันพยายามที่จะชัดเจน instantiate writer<N, I>
สำหรับการใด ๆ ที่ได้รับN
และI
ที่ควรได้รับอยู่แล้ว instantiated แล้วเสียงดังกราว ++ friend constexpr auto counter(slot<N>)
บ่นเกี่ยวกับนิยามใหม่
เพื่อทดสอบข้างต้นฉันได้เพิ่มอีกสองบรรทัดในซอร์สโค้ดก่อนหน้า
int test1 = counter(slot<11>());
int test2 = writer<11,0>::value;
คุณสามารถเห็นมันทั้งหมดด้วยตัวคุณเองใน godbolt ภาพหน้าจอด้านล่าง
ดังนั้นดูเหมือนว่าเสียงดังกราว ++ เชื่อว่ามันได้กำหนดสิ่งที่มันเชื่อว่ามันไม่ได้นิยามไว้ซึ่งทำให้หัวของคุณหมุนได้ใช่ไหม?
คำถามชุดที่สอง
- วิธีแก้ปัญหาทางกฎหมายของฉันคือC ++ เลยหรือฉันจัดการเพื่อค้นพบข้อผิดพลาด g ++ อีกครั้งหรือไม่?
- ถ้ามันถูกกฎหมายผมก็เลยพบข้อผิดพลาดเกี่ยวกับเสียงดังกราว ++ ที่น่ารังเกียจบ้างไหม?
- หรือว่าฉันเพิ่งเจาะลึกเข้าไปในโลกมืดของพฤติกรรมที่ไม่ได้กำหนดดังนั้นฉันจึงเป็นคนเดียวที่จะตำหนิ?
ไม่ว่าจะในกรณีใดฉันยินดีต้อนรับทุกคนอย่างอบอุ่นที่ต้องการช่วยฉันออกจากโพรงกระต่ายนี้โดยอธิบายคำอธิบายอาการปวดศีรษะหากจำเป็น : D
next()
ฟังก์ชั่น แต่ฉันไม่สามารถคิดออกได้ว่าวิธีการทำงาน ไม่ว่าในกรณีใดฉันจะได้รับการตอบสนองต่อปัญหาของฉันเองที่นี่: stackoverflow.com/a/60096865/566849