การอ้างอิงที่ไม่ได้กำหนดถึง constexpr แบบคงที่ []


186

ฉันต้องการstatic const charอาร์เรย์ในชั้นเรียนของฉัน GCC บ่นและบอกว่าฉันควรใช้constexprถึงแม้ว่าตอนนี้มันจะบอกฉันว่ามันเป็นข้อมูลอ้างอิงที่ไม่ได้กำหนด ถ้าฉันทำให้อาร์เรย์ไม่ใช่สมาชิกแล้วมันจะรวบรวม เกิดอะไรขึ้น?

// .hpp
struct foo {
  void bar();
  static constexpr char baz[] = "quz";
};

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}

1
เพียงลางสังหรณ์มันใช้งานได้ถ้า baz นั้นเป็นตัวอย่างหรือไม่ คุณสามารถเข้าถึงได้หรือไม่ มันอาจเป็นข้อผิดพลาด
FailedDev

1
@Pubby: คำถาม: หน่วยการแปลใดจะถูกกำหนดใน คำตอบ: ทุกสิ่งที่มีส่วนหัว ปัญหา: ละเมิดกฎข้อกำหนดหนึ่งข้อ ข้อยกเว้น: อินทิกรัลคอมไพล์เวลาคอมไพล์สามารถ "กำหนดค่าเริ่มต้น" ในส่วนหัว
Mooing Duck

มันรวบรวมได้ดีเป็นint@MooingDuck มันทำงานได้ดีในฐานะที่ไม่ใช่สมาชิก จะไม่เป็นการละเมิดกฎด้วยหรือ
Pubby

@ Pubby8: intสูตรโกง ในฐานะสมาชิกที่ไม่ควรได้รับอนุญาตเว้นแต่จะมีการเปลี่ยนแปลงกฎสำหรับ C ++ 11 (เป็นไปได้)
Mooing Duck

เมื่อพิจารณาถึงจำนวนการดูและอัปโหลดแล้วคำถามนี้ต้องการคำตอบที่ละเอียดยิ่งขึ้นซึ่งฉันได้เพิ่มไว้ด้านล่าง
Shafik Yaghmour

คำตอบ:


188

เพิ่มลงในไฟล์ cpp ของคุณ:

constexpr char foo::baz[];

เหตุผล: คุณต้องให้คำจำกัดความของสมาชิกแบบคงที่เช่นเดียวกับการประกาศ การประกาศและตัวกำหนดค่าเริ่มต้นจะอยู่ภายในคำจำกัดความของคลาส แต่ข้อกำหนดของสมาชิกจะต้องแยกต่างหาก


70
รูปลักษณ์ที่แปลก ... เพราะมันไม่ได้ดูเหมือนจะให้คอมไพเลอร์ที่มีข้อมูลบางอย่างมันไม่ได้มาก่อน ...
องุ่น

32
มันดูแปลกมากยิ่งขึ้นเมื่อคุณมีการประกาศคลาสของคุณในไฟล์. cpp! คุณเริ่มต้นฟิลด์ในการประกาศคลาส แต่คุณยังต้อง " ประกาศ " ฟิลด์โดยการเขียน constexpr char foo :: baz [] ใต้ชั้นเรียน ดูเหมือนว่าโปรแกรมเมอร์ที่ใช้ constexpr สามารถรวบรวมโปรแกรมของพวกเขาโดยทำตามเคล็ดลับแปลก ๆ : ประกาศอีกครั้ง
Lukasz Czerwinski

5
@LukaszCzerwinski: คำที่คุณต้องการคือ "define"
Kerrek SB

5
ถูกต้องไม่มีข้อมูลใหม่: ประกาศใช้decltype(foo::baz) constexpr foo::baz;
ไม่ใช่ผู้ใช้

6
การแสดงออกจะเป็นอย่างไรถ้า foo ถูกทำให้เป็นเทมเพลทส์? ขอบคุณ
Hei

80

C ++ 17 แนะนำตัวแปรอินไลน์

C ++ 17 แก้ไขปัญหานี้สำหรับconstexpr staticตัวแปรสมาชิกที่ต้องการคำนิยามแบบไม่อยู่ในขอบเขตหากไม่ได้ใช้ ดูครึ่งหลังของคำตอบนี้สำหรับรายละเอียดล่วงหน้า C ++ 17

ข้อเสนอP0386 ตัวแปรอินไลน์แนะนำความสามารถในการใช้ตัวinlineระบุกับตัวแปร โดยเฉพาะอย่างยิ่งกรณีนี้constexprหมายถึงinlineตัวแปรสมาชิกแบบคงที่ ข้อเสนอบอกว่า:

ตัวระบุแบบอินไลน์สามารถนำไปใช้กับตัวแปรเช่นเดียวกับฟังก์ชั่น ตัวแปรที่ประกาศแบบอินไลน์มีความหมายเช่นเดียวกับฟังก์ชั่นประกาศแบบอินไลน์: มันสามารถกำหนดได้เหมือนกันในหลายหน่วยการแปลจะต้องกำหนดไว้ในทุกหน่วยการแปลที่มีการใช้ odr และพฤติกรรมของโปรแกรมเป็นถ้า มีตัวแปรหนึ่งตัว

และแก้ไข [basic.def] p2:

การประกาศเป็นคำนิยามเว้นแต่
...

  • มันประกาศสมาชิกข้อมูลคงที่นอกคำจำกัดความของชั้นเรียนและตัวแปรที่ถูกกำหนดไว้ในชั้นเรียนที่มีตัวระบุ constexpr (การใช้งานนี้ถูกคัดค้าน; ดู [depr.static_constexpr])

...

และเพิ่ม[depr.static_constexpr] :

สำหรับความเข้ากันได้กับมาตรฐานสากล C ++ ก่อนหน้าสมาชิกข้อมูลคงที่ของ constexpr อาจถูกประกาศซ้ำซ้อนนอกคลาสโดยไม่มี initializer การใช้งานนี้เลิกใช้แล้ว [ตัวอย่าง:

struct A {
  static constexpr int n = 5;  // definition (declaration in C++ 2014)
};

constexpr int A::n;  // redundant declaration (definition in C++ 2014)

 - ตัวอย่างท้าย]


C ++ 14 และก่อนหน้านี้

ใน C ++ 03 เราได้รับอนุญาตเท่านั้นที่จะให้ initializers ในชั้นเรียนสำหรับปริพันธ์ constหรือประเภทการแจงนับ constใน C ++ 11 ใช้constexprนี้ก็ขยายไปถึงชนิดที่แท้จริง

ใน C ++ 11 เราไม่จำเป็นต้องให้คำจำกัดความของขอบเขตเนมสเปซสำหรับconstexprสมาชิกแบบสแตติกถ้ามันไม่ได้ถูกใช้แล้วเราสามารถดูได้จากส่วนมาตรฐาน C ++ 11 ฉบับร่าง9.4.2 [class.static.data]ซึ่งระบุว่า ( เน้นที่เหมืองไปข้างหน้า ):

[... ] สมาชิกข้อมูลแบบคงที่ของประเภทตัวอักษรสามารถประกาศในคำจำกัดความของชั้นเรียนด้วยตัวระบุ constexpr; ถ้าเป็นเช่นนั้นการประกาศจะต้องระบุวงเล็บปีกกา - หรือ - เท่ากับ - initializer ที่ทุก initializer-clause นั่นคือการมอบหมาย - การแสดงออกคือการแสดงออกคงที่ [หมายเหตุ: ในทั้งสองกรณีนี้สมาชิกอาจปรากฏในนิพจน์คงที่ - บันทึกย่อ] สมาชิกจะยังคงถูกกำหนดในขอบเขตเนมสเปซถ้าใช้ odr (3.2)ในโปรแกรมและคำจำกัดความขอบเขตเนมสเปซจะต้องไม่มีตัวกำหนดค่าเริ่มต้น

ดังนั้นคำถามจะกลายเป็นbaz ใช้ไม่ได้ที่นี่:

std::string str(baz); 

และคำตอบคือใช่และเราจำเป็นต้องกำหนดขอบเขตเนมสเปซด้วย

แล้วเราจะทราบได้อย่างไรว่าตัวแปรนั้นใช้odrหรือไม่? ถ้อยคำ C ++ 11 ต้นฉบับในส่วน3.2 [basic.def.odr]พูดว่า:

การแสดงออกอาจมีการประเมินเว้นแต่ว่ามันเป็นตัวถูกดำเนินการประเมิน (ข้อ 5) หรือนิพจน์ย่อยของมัน ตัวแปรที่มีชื่อปรากฏเป็นการแสดงออกอาจประเมินเป็น ODR-ใช้จนกว่า มันเป็นวัตถุที่ตอบสนองความต้องการสำหรับการปรากฏตัวในการแสดงออกคงที่ (5.19) และการแปลง lvalue ไป rvalue (4.1) จะถูกนำไปใช้ทันที

ดังนั้นbazการแสดงออกคงที่แต่การแปลง lvalue-to-rvalueไม่ได้ถูกนำมาใช้ทันทีเพราะมันไม่สามารถใช้งานได้เนื่องจากbazเป็นอาร์เรย์ สิ่งนี้กล่าวถึงในหมวด4.1 [conv.lval]ซึ่งกล่าวว่า:

glvalue (3.10) ของประเภทที่ไม่ใช่ฟังก์ชั่นที่ไม่ใช่อาร์เรย์ Tสามารถแปลงเป็น prvalue.53 [... ]

สิ่งที่ถูกนำไปใช้ในการแปลงอาร์เรย์ไปชี้

ถ้อยคำของ[basic.def.odr]นี้มีการเปลี่ยนแปลงเนื่องจากรายงานข้อบกพร่อง 712เนื่องจากบางกรณีไม่ได้ครอบคลุมโดยถ้อยคำนี้ แต่การเปลี่ยนแปลงเหล่านี้ไม่ได้เปลี่ยนผลลัพธ์สำหรับกรณีนี้


ดังนั้นเราชัดเจนว่าconstexprไม่มีอะไรเกี่ยวข้องกับมันใช่หรือไม่ ( bazเป็นนิพจน์คงที่ต่อไป)
MM

@MattMcNabb ดีconstexprเป็นสิ่งจำเป็นในกรณีที่สมาชิกไม่ได้เป็นintegral or enumeration typeแต่อย่างอื่นใช่สิ่งที่สำคัญก็คือว่ามันเป็นแสดงออกอย่างต่อเนื่อง
Shafik Yaghmour

ในย่อหน้าแรก "ord-used" ควรอ่านเป็น "odr-used" ฉันเชื่อ แต่ฉันไม่แน่ใจกับ C ++
Egor Pasko

37

นี่เป็นข้อบกพร่องใน C ++ 11 - อย่างที่คนอื่น ๆ ได้อธิบายไว้ใน C ++ 11 ตัวแปรสมาชิกแบบคงที่ซึ่งแตกต่างจากตัวแปรโกลบอลชนิดอื่นทุกชนิดมีการเชื่อมโยงภายนอกดังนั้นจึงต้องมีการเชื่อมโยงภายนอกอย่างชัดเจน

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

ข่าวดีแม้ว่า - ข้อบกพร่องนี้ได้รับการแก้ไขใน C ++ 17! วิธีการที่เป็นบิตซับซ้อนแม้ว่า: ใน C ++ 17 ตัวแปรสมาชิก constexpr คงเป็นแบบอินไลน์โดยปริยาย การมีอินไลน์นำไปใช้กับตัวแปรเป็นแนวคิดใหม่ใน C ++ 17 แต่มันมีประสิทธิภาพหมายความว่าพวกเขาไม่ต้องการคำจำกัดความที่ชัดเจนที่ใดก็ได้


4
สูงถึง C ++ 17 ข้อมูล คุณสามารถเพิ่มข้อมูลนี้เพื่อตอบรับได้!
SR

5

ไม่ใช่วิธีที่สวยงามกว่านี้ที่จะเปลี่ยนchar[]เป็น:

static constexpr char * baz = "quz";

วิธีนี้เราสามารถมีนิยาม / การประกาศ / initializer ใน 1 บรรทัดของรหัส


9
กับchar[]คุณสามารถใช้sizeofเพื่อให้ได้ความยาวของสตริงในเวลารวบรวมโดยที่char *คุณไม่สามารถ (มันจะกลับความกว้างของประเภทตัวชี้ 1 ในกรณีนี้)
gnzlbg

2
สิ่งนี้จะสร้างคำเตือนหากคุณต้องการเข้มงวดกับ ISO C ++ 11
Shital Shah

ดูคำตอบของฉันซึ่งไม่ได้แสดงให้เห็นsizeofปัญหาและสามารถนำมาใช้ใน "ส่วนหัวเท่านั้น" การแก้ปัญหา
จอช Greifer

4

วิธีแก้ปัญหาสำหรับการเชื่อมโยงภายนอกของสมาชิกแบบคงที่คือการใช้constexprgetters สมาชิกอ้างอิง (ซึ่งไม่พบปัญหา @gnzlbg ที่ยกขึ้นเป็นความคิดเห็นต่อคำตอบจาก @deddebme)
สำนวนนี้มีความสำคัญต่อฉันเพราะฉันเกลียดที่จะมีไฟล์. cpp หลายไฟล์ในโครงการของฉันและพยายาม จำกัด จำนวนไว้ที่หนึ่งซึ่งประกอบด้วยอะไร แต่#includes และmain()ฟังก์ชั่น

// foo.hpp
struct foo {
  static constexpr auto& baz() { return "quz"; }
};

// some.cpp

  auto sz = sizeof(foo::baz()); // sz == 4

  auto& foo_baz = foo::baz();  // note auto& not auto
  auto sz2 =  sizeof(foo_baz);    // 4
  auto name = typeid(foo_baz).name();  // something like 'char const[4]'

-1

ในสภาพแวดล้อมของฉัน gcc vesion คือ 5.4.0 การเพิ่ม "-O2" สามารถแก้ไขข้อผิดพลาดในการรวบรวมนี้ ดูเหมือนว่า gcc สามารถจัดการกรณีนี้เมื่อขอการเพิ่มประสิทธิภาพ

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