ทำไมเราต้องมีความต้องการ?


161

หนึ่งในมุมของ C ++ 20 requires requiresแนวคิดก็คือว่ามีบางสถานการณ์ที่คุณต้องเขียน ตัวอย่างเช่นตัวอย่างนี้จาก[expr.prim.req] / 3 :

ต้องแสดงออกนอกจากนี้ยังสามารถนำมาใช้ในต้องมีข้อ ([ชั่วคราว]) เป็นวิธีการเขียนโฆษณา จำกัด เฉพาะกิจข้อโต้แย้งแม่แบบดังกล่าวเป็นหนึ่งด้านล่าง:

template<typename T>
  requires requires (T x) { x + x; }
    T add(T a, T b) { return a + b; }

เป็นครั้งแรกที่ต้องเปิดต้องใช้ข้อและแนะนำที่สองต้องแสดงออก

เหตุผลทางเทคนิคที่อยู่เบื้องหลังrequiresคำหลักที่ต้องการนั้นคืออะไร ทำไมเราไม่อนุญาตให้เขียน:

template<typename T>
  requires (T x) { x + x; }
    T add(T a, T b) { return a + b; }

(หมายเหตุ: โปรดอย่าตอบว่าไวยากรณ์requiresนั้น)


25
คำแนะนำ: "มีอะไรที่ต้องมีความต้องการหรือไม่?" noexcept(noexcept(...))อย่างจริงจังมากขึ้นผมมีลางสังหรณ์ว่ามันเป็นเหตุผลที่อยู่เบื้องหลังเดียวกัน
เควนติ

11
ทั้งสองrequiresเป็นคำพ้องความหมายในความคิดของฉัน: พวกเขาดูเหมือนกันสะกดเหมือนกันได้กลิ่นเหมือนกัน แต่มีความแตกต่างกันอย่างแท้จริง ถ้าฉันจะแนะนำการแก้ไขฉันขอแนะนำให้เปลี่ยนชื่อหนึ่งในนั้น
YSC

5
@YSC - co_requires? (ขออภัยไม่สามารถต้านทานได้)
StoryTeller - Unslander Monica

122
ความบ้าจะหยุดที่ไหน? long longสิ่งต่อไปที่คุณรู้ว่าเราจะต้อง
Eljay

8
@StoryTeller: "ต้องการต้องมีหรือไม่" จะได้รับการพยัญชนะมากยิ่งขึ้น !!
PW

คำตอบ:


81

เป็นเพราะไวยากรณ์จำเป็นต้องใช้ มันทำ

requiresจำกัด ไม่ต้องใช้requiresการแสดงออก มันสามารถใช้นิพจน์ค่าคงที่บูลีนใดก็ได้ที่มากขึ้นหรือน้อยลง ดังนั้นrequires (foo)จะต้องเป็นrequiresข้อ จำกัด ที่ถูกต้องตามกฎหมาย

requires แสดงออก (สิ่งที่ว่าการทดสอบไม่ว่าจะเป็นบางสิ่งบางอย่างเป็นไปตามข้อ จำกัด ) เป็นโครงสร้างที่แตกต่างกัน; มันเพิ่งเปิดตัวโดยคำหลักเดียวกัน requires (foo f)จะเป็นจุดเริ่มต้นของการrequiresแสดงออกที่ถูกต้อง

สิ่งที่คุณต้องการคือถ้าคุณใช้requiresในสถานที่ที่ยอมรับข้อ จำกัด คุณควรจะสามารถสร้าง "ข้อ จำกัด + นิพจน์" จากrequiresประโยค

ดังนั้นนี่คือคำถาม: ถ้าคุณใส่requires (foo)เข้าไปในสถานที่ที่เหมาะสมสำหรับข้อ จำกัด ที่ต้องการ ... ตัวแยกวิเคราะห์จะต้องไปไกลแค่ไหนก่อนที่จะสามารถรู้ได้ว่านี่เป็นข้อ จำกัด ที่ต้องการแทนที่จะเป็นข้อ จำกัด + แสดงออกในแบบที่คุณต้องการ เป็น?

พิจารณาสิ่งนี้:

void bar() requires (foo)
{
  //stuff
}

ถ้าfooเป็นประเภทดังนั้น(foo)รายการพารามิเตอร์ของนิพจน์ที่ต้องการคือและทุกอย่างในตัว{}ไม่ใช่ฟังก์ชั่น แต่เป็นเนื้อความของrequiresนิพจน์นั้น มิฉะนั้นfooเป็นนิพจน์ในrequiresข้อ

คุณสามารถบอกได้ว่าคอมไพเลอร์ควรหาว่าอะไรfooคือสิ่งแรก แต่ C ++ ไม่ชอบจริง ๆเมื่อการวิเคราะห์ขั้นพื้นฐานของการเรียงลำดับโทเค็นนั้นต้องการให้คอมไพเลอร์เข้าใจความหมายของตัวระบุเหล่านั้นก่อนที่มันจะสามารถเข้าใจโทเค็นได้ ใช่ C ++ ไวต่อบริบทดังนั้นสิ่งนี้จะเกิดขึ้น แต่คณะกรรมการจะหลีกเลี่ยงเมื่อเป็นไปได้

ใช่แล้วมันเป็นไวยกรณ์


2
มันสมเหตุสมผลไหมที่จะมีรายการพารามิเตอร์ที่มีชนิด แต่ไม่มีชื่อ?
NathanOliver

3
@Quentin: มีบางกรณีที่ต้องพึ่งพาบริบทในไวยากรณ์ C ++ แต่คณะกรรมการจริงๆไม่พยายามที่จะลดนั้นและแน่นอนพวกเขาไม่ได้เช่นการเพิ่มมากขึ้น
Nicol Bolas

3
@RobertAndrzejuk: หากrequiresปรากฏขึ้นหลัง<>ชุดอาร์กิวเมนต์ของเทมเพลตหรือหลังจากรายการพารามิเตอร์ของฟังก์ชันแสดงว่ามันเป็นส่วนคำสั่งที่ต้องมี หากrequiresปรากฏขึ้นในที่ที่การแสดงออกที่ถูกต้องแล้วมันเป็น require-expression สิ่งนี้สามารถถูกกำหนดโดยโครงสร้างของการแยกวิเคราะห์ต้นไม้ไม่ใช่เนื้อหาของการแยกวิเคราะห์ต้นไม้ (ข้อมูลเฉพาะของวิธีการที่ตัวระบุได้รับการกำหนดจะเป็นเนื้อหาของต้นไม้)
Nicol Bolas

6
@RobertAndrzejuk: แน่นอนต้องใช้นิพจน์อาจใช้คำหลักอื่น แต่คำหลักมีขนาดใหญ่มากค่าใช้จ่ายใน C ++ เช่นที่พวกเขามีศักยภาพที่จะทำลายโปรแกรมที่ใช้ตัวบ่งชี้ที่ได้กลายเป็นคำหลักที่ ข้อเสนอแนวความคิดแล้วนำสองคำ: และconcept requiresแนะนำครั้งที่สามเมื่อกรณีที่สองสามารถครอบคลุมทั้งสองกรณีโดยไม่มีปัญหาทางไวยากรณ์และปัญหาที่ผู้ใช้พบเจอเพียงเล็กน้อยก็สิ้นเปลือง ท้ายที่สุดปัญหาทางภาพปัญหาเดียวคือมีคำซ้ำสองครั้ง
Nicol Bolas

3
@RobertAndrzejuk มันเป็นการปฏิบัติที่ไม่ดีอยู่แล้วสำหรับข้อ จำกัด แบบอินไลน์เช่นนั้นเนื่องจากคุณไม่ได้รับการบอกรับสมาชิกราวกับว่าคุณได้เขียนแนวคิด ดังนั้นการใช้ตัวระบุสำหรับคุณลักษณะที่ไม่มีการใช้งานต่ำแนะนำจึงไม่ใช่ความคิดที่ดี
Rakete1111

60

noexcept(noexcept(...))สถานการณ์เป็นสิ่งที่คล้ายคลึงกับ แน่นอนว่านี่ฟังดูแย่กว่าเรื่องดี แต่ขออธิบาย :) เราจะเริ่มจากสิ่งที่คุณรู้แล้ว:

C ++ 11 มี " noexcept-clauses" และ " noexcept-expressions" พวกเขาทำสิ่งต่าง ๆ

  • A noexcept--clause พูดว่า "ฟังก์ชั่นนี้ควรจะไม่มีข้อยกเว้นเมื่อ ... (เงื่อนไขบางประการ)" มันไปในการประกาศฟังก์ชั่นใช้พารามิเตอร์บูลีนและทำให้เกิดการเปลี่ยนแปลงพฤติกรรมในฟังก์ชั่นที่ประกาศ

  • A noexcept-expression บอกว่า "คอมไพเลอร์โปรดบอกฉันว่า (การแสดงออกบางอย่าง) นั้นไม่มีข้อยกเว้นหรือไม่" มันเป็นนิพจน์บูลีน มันไม่มี "ผลข้างเคียง" ต่อพฤติกรรมของโปรแกรม - มันแค่ขอให้คอมไพเลอร์หาคำตอบสำหรับคำถามใช่ / ไม่ใช่ "การแสดงออกนี้ไม่มีข้อยกเว้นหรือไม่"

เรา สามารถซ้อนnoexcept-expression ไว้ในnoexcept-clause แต่โดยทั่วไปแล้วเราถือว่ามันเป็นสไตล์ที่ไม่ดีที่จะทำ

template<class T>
void incr(T t) noexcept(noexcept(++t));  // NOT SO HOT

มันถือว่าเป็นสไตล์ที่ดีกว่าในการแค็ปnoexceptซูล -expression ในลักษณะประเภท

template<class T> inline constexpr bool is_nothrow_incrable_v =
    noexcept(++std::declval<T&>());  // BETTER, PART 1

template<class T>
void incr(T t) noexcept(is_nothrow_incrable_v<T>);  // BETTER, PART 2

ร่างการทำงาน C ++ 2a มี " requires-clauses" และ " requires-expressions" พวกเขาทำสิ่งต่าง ๆ

  • A requires--clause พูดว่า "ฟังก์ชั่นนี้ควรมีส่วนร่วมในการแก้ปัญหาการโอเวอร์โหลดเมื่อ ... (บางเงื่อนไข)" มันไปในการประกาศฟังก์ชั่นใช้พารามิเตอร์บูลีนและทำให้เกิดการเปลี่ยนแปลงพฤติกรรมในฟังก์ชั่นที่ประกาศ

  • คำrequiresพูดบอกว่า "คอมไพเลอร์โปรดบอกฉันว่า (การแสดงออกบางชุด) นั้นดีหรือไม่" มันเป็นนิพจน์บูลีน มันไม่มี "ผลข้างเคียง" ต่อพฤติกรรมของโปรแกรม - มันแค่ขอให้คอมไพเลอร์หาคำตอบสำหรับคำถามใช่ / ไม่ใช่ "การแสดงออกนี้มีรูปแบบที่ดีหรือไม่"

เราสามารถทำรัง - requiresแสดงออกภายในrequires-clause แต่โดยทั่วไปแล้วเราถือว่ามันเป็นสไตล์ที่ไม่ดีที่จะทำ

template<class T>
void incr(T t) requires (requires(T t) { ++t; });  // NOT SO HOT

มันถือว่าเป็นสไตล์ที่ดีกว่าในการแค็ปrequiresซูล -expression ในลักษณะประเภท ...

template<class T> inline constexpr bool is_incrable_v =
    requires(T t) { ++t; };  // BETTER, PART 1

template<class T>
void incr(T t) requires is_incrable_v<T>;  // BETTER, PART 2

... หรือในแนวคิด (C ++ 2a Working Draft)

template<class T> concept Incrable =
    requires(T t) { ++t; };  // BETTER, PART 1

template<class T>
void incr(T t) requires Incrable<T>;  // BETTER, PART 2

1
ฉันไม่ได้ซื้ออาร์กิวเมนต์นี้จริงๆ noexceptมีปัญหาที่noexcept(f())อาจหมายถึงทั้งตีความf()เป็นแบบบูลที่เราใช้ในการตั้งสเปคหรือตรวจสอบหรือไม่เป็นf() ไม่มีความกำกวมนี้เนื่องจากการแสดงออกของการตรวจสอบความถูกต้องจะต้องมีการแนะนำด้วยs หลังจากนั้นข้อโต้แย้งก็คือ "ไวยากรณ์พูดอย่างนั้น" noexceptrequires{}
Barry

@Barry: ดูความคิดเห็นนี้ ดูเหมือนว่า{}จะเป็นตัวเลือก
Eric

1
@Eric The {}ไม่ใช่ตัวเลือกนั่นไม่ใช่สิ่งที่แสดงความคิดเห็น อย่างไรก็ตามนั่นเป็นความคิดเห็นที่ดีที่แสดงให้เห็นถึงการแยกวิเคราะห์ความกำกวม อาจยอมรับความคิดเห็นนั้น (พร้อมคำอธิบาย) เป็นคำตอบแบบสแตนด์อโลน
Barry

1
requires is_nothrow_incrable_v<T>;ควรเป็นrequires is_incrable_v<T>;
Ruslan

16

ฉันคิดว่าหน้าแนวคิดของ cppreferenceอธิบายสิ่งนี้ ฉันสามารถอธิบายด้วย "คณิตศาสตร์" เพื่อที่จะพูดว่าทำไมต้องเป็นเช่นนี้:

หากคุณต้องการกำหนดแนวคิดคุณทำสิ่งนี้:

template<typename T>
concept Addable = requires (T x) { x + x; }; // requires-expression

ถ้าคุณต้องการประกาศฟังก์ชั่นที่ใช้แนวคิดนั้นคุณต้องทำสิ่งนี้:

template<typename T> requires Addable<T> // requires-clause, not requires-expression
T add(T a, T b) { return a + b; }

ทีนี้ถ้าคุณไม่ต้องการนิยามแนวคิดแยกจากกันฉันคิดว่าสิ่งที่คุณต้องทำคือการทดแทน ใช้ส่วนนี้requires (T x) { x + x; };และแทนที่Addable<T>ส่วนและคุณจะได้รับ:

template<typename T> requires requires (T x) { x + x; }
T add(T a, T b) { return a + b; }

สิ่งที่คุณถามเกี่ยวกับ


4
ฉันไม่คิดว่าเป็นคำถามที่ถาม นี่คือการอธิบายไวยากรณ์มากหรือน้อย
สัญจรภายใน

แต่ทำไมคุณถึงไม่มีtemplate<typename T> requires (T x) { x + x; }และต้องการสิ่งนั้นไม่ได้ทั้งประโยคและการแสดงออก?
NathanOliver

2
@NathanOliver: เพราะคุณกำลังบังคับให้คอมไพเลอร์ตีความโครงสร้างหนึ่งเป็นอีกโครงสร้าง requiresข้อ -as- จำกัด ไม่ต้องเป็นrequires-expression นั่นเป็นเพียงการใช้งานที่เป็นไปได้เพียงอย่างเดียว
Nicol Bolas

2
@TheQuantumPhysicist สิ่งที่ฉันได้รับพร้อมกับความคิดเห็นของฉันคือคำตอบนี้เพียงอธิบายไวยากรณ์ requires requiresไม่ว่าเหตุผลทางเทคนิคที่เกิดขึ้นจริงที่เราต้อง พวกเขาสามารถเพิ่มบางอย่างในไวยากรณ์เพื่ออนุญาตtemplate<typename T> requires (T x) { x + x; }แต่ไม่ได้ Barry อยากรู้ว่าทำไมพวกเขาถึงไม่ทำ
NathanOliver

3
ถ้าเราเล่นจริงด้วยไวยากรณ์ - ความคลุมเครือตรงนี้โอเคฉันจะกัด godbolt.org/z/i6n8kM template<class T> void f(T) requires requires(T (x)) { (void)x; };หมายถึงสิ่งที่แตกต่างหากคุณลบหนึ่งในrequireses
Quuxplusone

12

ฉันพบความคิดเห็นจาก Andrew Sutton (หนึ่งในผู้แต่ง Concepts ที่นำมาใช้ใน gcc) เพื่อเป็นประโยชน์ในเรื่องนี้ดังนั้นฉันคิดว่าฉันจะพูดที่นี่อย่างใกล้ชิด:

ไม่นานที่ผ่านมาต้องการ - นิพจน์ (วลีที่แนะนำโดยที่สองที่ต้องการ) ไม่ได้รับอนุญาตในการ จำกัด - นิพจน์ (วลีที่นำเสนอโดยต้องการครั้งแรก) มันสามารถปรากฏในคำจำกัดความของแนวคิดเท่านั้น อันที่จริงนี่เป็นสิ่งที่เสนอในส่วนของกระดาษที่มีการอ้างสิทธิ์

อย่างไรก็ตามในปี 2559 มีข้อเสนอที่จะผ่อนคลายข้อ จำกัด [หมายเหตุบรรณาธิการ: P0266 ] สังเกตการหยุดงานของวรรค 4 ในส่วนที่ 4 ของบทความ และดังนั้นจึงต้องเกิด

เพื่อบอกความจริงฉันไม่เคยใช้ข้อ จำกัด นั้นใน GCC ดังนั้นจึงเป็นไปได้เสมอ ฉันคิดว่าวอลเตอร์อาจค้นพบสิ่งนั้นและพบว่ามีประโยชน์นำไปสู่บทความนั้น

เกรงว่าใคร ๆ คิดว่าฉันไม่ไวต่อการเขียนต้องใช้สองครั้งฉันใช้เวลาพยายามหาว่ามันง่ายหรือไม่ คำตอบสั้น ๆ : ไม่

ปัญหาคือมีโครงสร้างทางไวยากรณ์สองรายการที่จำเป็นต้องนำเสนอหลังจากรายการพารามิเตอร์เทมเพลต: โดยทั่วไปแล้วจะมีการแสดงออกของข้อ จำกัด (เช่นP && Q) และความต้องการทางไวยากรณ์เป็นครั้งคราว (เช่นrequires (T a) { ... }) นั่นเรียกว่าต้องการการแสดงออก

ก่อนอื่นต้องมีการแนะนำข้อ จำกัด ข้อที่สองต้องการแนะนำการใช้งาน นั่นเป็นเพียงวิธีการเรียบเรียงไวยากรณ์ ฉันไม่คิดว่ามันจะสับสน

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

ดังนั้นคุณต้องเลือก: make ต้องการแนะนำนิพจน์ (เหมือนตอนนี้) หรือทำให้มันแนะนำรายการข้อกำหนดแบบพารามิเตอร์

ฉันเลือกวิธีการปัจจุบันเพราะส่วนใหญ่เวลา (ในเกือบ 100% ของเวลา) ฉันต้องการสิ่งอื่นนอกเหนือจากต้องการนิพจน์ และในกรณีที่หายากเหลือเกินฉันต้องการความต้องการนิพจน์สำหรับข้อ จำกัด เฉพาะกิจฉันไม่รังเกียจที่จะเขียนคำสองครั้ง เป็นตัวบ่งชี้ที่ชัดเจนว่าฉันไม่ได้พัฒนาสิ่งที่เป็นนามธรรมสำหรับเทมเพลต (เพราะถ้าฉันมีก็จะมีชื่อ)

ฉันสามารถเลือกที่จะทำให้สิ่งที่ต้องการนำมาซึ่งความต้องการ - แสดงออก ที่จริงแล้วแย่กว่านั้นเพราะข้อ จำกัด ของคุณทั้งหมดจะเริ่มเป็นเช่นนี้:

template<typename T>
  requires { requires Eq<T>; }
void f(T a, T b);

ที่นี่ต้องการที่ 2 เรียกว่าข้อกำหนดซ้อน มันประเมินการแสดงออก (รหัสอื่น ๆ ในบล็อกของ require-expression ไม่ได้รับการประเมิน) ฉันคิดว่านี่เป็นวิธีที่เลวร้ายยิ่งกว่าสภาพที่เป็นอยู่ ตอนนี้คุณจะต้องเขียนสองครั้งทุกที่

ฉันสามารถใช้คำหลักเพิ่มเติมได้ นี่เป็นปัญหาในสิทธิของตนเอง --- และไม่ใช่แค่การปลดจักรยาน อาจมีวิธีในการ "แจกจ่ายซ้ำ" คำหลักเพื่อหลีกเลี่ยงการทำซ้ำ แต่ฉันไม่ได้คิดอย่างจริงจัง แต่นั่นไม่ได้เปลี่ยนแก่นแท้ของปัญหา


-10

เนื่องจากคุณกำลังพูดว่าสิ่ง A มีข้อกำหนด B และข้อกำหนด B มีข้อกำหนด C

สิ่ง A ต้องใช้ B ซึ่งจะต้องใช้ C

คำสั่ง "ต้องการ" ตัวเองต้องการบางสิ่งบางอย่าง

คุณมีสิ่ง A (ต้องการ B (ต้องการ C))

Meh :)


4
แต่ตามคำตอบอื่น ๆ ที่หนึ่งและที่สองrequiresไม่ได้เป็นแนวคิดในสิ่งเดียวกัน (หนึ่งคือประโยคหนึ่งการแสดงออก) ในความเป็นจริงถ้าผมเข้าใจถูกต้องทั้งสองชุด()ในrequires (requires (T x) { x + x; })มีความหมายที่แตกต่างกันมาก (เสริมความเป็นอยู่ด้านนอกและมักจะมี constexpr บูล; ภายในเป็นส่วนจำเป็นของการแนะนำต้องแสดงออกและไม่ได้ช่วยให้การแสดงออกที่เกิดขึ้นจริง)
Max Langhof

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