ความยาวการคำนวณของสตริง C ในเวลาคอมไพล์ นี่คือ constexpr จริงๆหรือ?


95

ฉันกำลังพยายามคำนวณความยาวของสตริงลิเทอรัลในเวลาคอมไพล์ ในการทำเช่นนั้นฉันใช้รหัสต่อไปนี้:

#include <cstdio>

int constexpr length(const char* str)
{
    return *str ? 1 + length(str + 1) : 0;
}

int main()
{
    printf("%d %d", length("abcd"), length("abcdefgh"));
}

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

0x100000f5e:  leaq   0x35(%rip), %rdi          ; "%d %d"
0x100000f65:  movl   $0x4, %esi
0x100000f6a:  movl   $0x8, %edx
0x100000f6f:  xorl   %eax, %eax
0x100000f71:  callq  0x100000f7a               ; symbol stub for: printf

คำถามของฉัน: มีการรับประกันตามมาตรฐานหรือไม่ว่าlengthฟังก์ชันจะได้รับการประเมินเวลาคอมไพล์

หากนี่เป็นความจริงประตูสำหรับการคอมไพล์การคำนวณตัวอักษรสตริงเวลาเพิ่งเปิดให้ฉัน ... ตัวอย่างเช่นฉันสามารถคำนวณแฮชในเวลาคอมไพล์และอื่น ๆ อีกมากมาย ...


3
ตราบใดที่พารามิเตอร์เป็นนิพจน์คงที่จะต้องเป็น
chris

1
@chris มีการรับประกันหรือไม่ว่าบางสิ่งที่สามารถเป็นนิพจน์คงที่จะต้องได้รับการประเมิน ณ เวลาคอมไพล์เมื่อใช้ในบริบทที่ไม่ต้องการนิพจน์คงที่?
TC

12
BTW รวมถึงการ<cstdio>โทรแล้ว::printfไม่สามารถพกพาได้ มาตรฐานกำหนด<cstdio>ให้std::printfเท่านั้น
Ben Voigt

1
@BenVoigt โอเคขอบคุณที่ชี้ให้เห็น :) ตอนแรกฉันใช้ std :: cout แต่โค้ดที่สร้างขึ้นค่อนข้างใหญ่ในการค้นหาค่าที่แท้จริง :)
Mircea Ispas

3
@Felics ฉันมักใช้godboltเมื่อตอบคำถามเกี่ยวกับการเพิ่มประสิทธิภาพและการใช้งานprintfอาจทำให้โค้ดน้อยลงอย่างมากในการจัดการ
Shafik Yaghmour

คำตอบ:


76

ไม่รับประกันว่านิพจน์คงที่จะได้รับการประเมินในเวลาคอมไพล์เรามีเพียงคำพูดที่ไม่ใช่บรรทัดฐานจากร่างมาตรฐาน C ++ส่วน5.19 นิพจน์ค่าคงที่ระบุว่า:

[... ]> [หมายเหตุ: นิพจน์คงที่สามารถประเมินได้ระหว่างการแปล - หมายเหตุตอนท้าย]

คุณสามารถกำหนดผลลัพธ์ให้กับconstexprตัวแปรเพื่อให้แน่ใจว่าได้รับการประเมินในเวลาคอมไพล์เราสามารถดูสิ่งนี้ได้จากการอ้างอิง C ++ 11 ของ Bjarne Stroustrupซึ่งระบุว่า ( เน้นของฉัน ):

นอกจากจะสามารถประเมินนิพจน์ในเวลาคอมไพล์แล้วเรายังต้องการให้สามารถประเมินนิพจน์ในขณะคอมไพล์ constexpr หน้านิยามตัวแปรทำเช่นนั้น (และหมายถึง const):

ตัวอย่างเช่น:

constexpr int len1 = length("abcd") ;

Bjarne Stroustrup ให้ข้อมูลสรุปว่าเมื่อใดที่เราสามารถรับรองการประเมินเวลาคอมไพล์ในรายการบล็อก isocppนี้และกล่าวว่า:

[... ] คำตอบที่ถูกต้อง - ตามที่ระบุโดย Herb คือตามมาตรฐานฟังก์ชัน constexpr อาจได้รับการประเมินในเวลาคอมไพเลอร์หรือรันไทม์เว้นแต่จะใช้เป็นนิพจน์คงที่ซึ่งในกรณีนี้จะต้องได้รับการประเมินที่คอมไพล์ -เวลา. เพื่อรับประกันการประเมินเวลาคอมไพล์เราต้องใช้ในกรณีที่จำเป็นต้องใช้นิพจน์คงที่ (เช่นอาร์เรย์ที่ผูกไว้หรือเป็นเลเบลเคส) หรือใช้เพื่อเริ่มต้น constexpr ฉันหวังว่าจะไม่มีคอมไพเลอร์ที่เคารพตัวเองที่จะพลาดโอกาสในการเพิ่มประสิทธิภาพในการทำสิ่งที่ฉันเคยกล่าวไว้: "ฟังก์ชัน constexpr จะได้รับการประเมินในเวลาคอมไพล์หากอาร์กิวเมนต์ทั้งหมดเป็นนิพจน์คงที่"

ดังนั้นจึงสรุปสองกรณีที่ควรได้รับการประเมินในเวลาคอมไพล์:

  1. ใช้ในกรณีที่จำเป็นต้องใช้นิพจน์คงที่ซึ่งดูเหมือนว่าจะอยู่ที่ใดก็ได้ในมาตรฐานแบบร่างที่วลีshall be ... converted constant expressionหรือshall be ... constant expressionถูกใช้เช่นอาร์เรย์ที่ผูกไว้
  2. ใช้เพื่อเริ่มต้นconstexprตามที่ฉันร่างไว้ด้านบน

4
ที่กล่าวว่าโดยหลักการแล้วคอมไพเลอร์มีสิทธิ์ที่จะเห็นวัตถุที่มีการเชื่อมโยงภายในหรือไม่มีเลยconstexpr int x = 5;สังเกตว่ามันไม่ต้องการค่าในเวลาคอมไพล์ (สมมติว่ามันไม่ได้ใช้เป็นพารามิเตอร์เทมเพลตหรืออะไรก็ตาม) และปล่อยออกมาจริงๆ โค้ดที่คำนวณค่าเริ่มต้นที่รันไทม์โดยใช้ 5 ค่าทันทีของ 1 และ 4 อ็อพชันเพิ่มเติม ตัวอย่างที่เป็นจริงมากขึ้น: คอมไพเลอร์อาจถึงขีด จำกัด การเรียกซ้ำและเลื่อนการคำนวณไปจนถึงรันไทม์ เว้นแต่คุณจะทำบางอย่างที่บังคับให้คอมไพเลอร์ใช้ค่าจริง "รับประกันว่าจะได้รับการประเมินในเวลาคอมไพล์" เป็นปัญหา QOI
Steve Jessop

@SteveJessop Bjarne ดูเหมือนจะใช้แนวคิดที่ไม่มีอะนาล็อกที่ฉันสามารถหาได้ในร่างมาตรฐานซึ่งใช้เป็นนิพจน์คงที่หมายถึงการประเมินในการแปล ดังนั้นดูเหมือนว่ามาตรฐานไม่ได้ระบุอย่างชัดเจนว่าเขากำลังพูดอะไรดังนั้นฉันมักจะเห็นด้วยกับคุณ แม้ว่าทั้ง Bjarne และ Herb ดูเหมือนจะเห็นด้วยกับเรื่องนี้ซึ่งอาจบ่งชี้ว่าเป็นเพียงการระบุ
Shafik Yaghmour

2
ฉันคิดว่าพวกเขาทั้งคู่กำลังพิจารณาเฉพาะ "คอมไพเลอร์ที่เคารพตนเอง" ซึ่งตรงข้ามกับคอมไพเลอร์ที่เป็นไปตามมาตรฐาน แต่ตั้งใจจะขัดขวางคอมไพเลอร์ที่ฉันตั้งสมมติฐาน มันมีประโยชน์ในการให้เหตุผลเกี่ยวกับสิ่งที่มาตรฐานรับรองจริงไม่ใช่อย่างอื่น ;-)
Steve Jessop

3
@SteveJessop จงใจขัดขวางคอมไพเลอร์เช่น Hell ++ ที่น่าอับอาย (และน่าเสียดายที่ไม่มีอยู่จริง) สิ่งนี้จะดีมากสำหรับการทดสอบความสอดคล้อง / การพกพา
Angew ไม่ภูมิใจใน SO

ภายใต้กฎ as-if แม้การใช้ค่าเป็นค่าคงที่ของเวลาในการคอมไพล์ก็ยังไม่เพียงพอ: คอมไพเลอร์มีอิสระที่จะจัดส่งสำเนาของแหล่งที่มาของคุณและคอมไพล์ใหม่ที่รันไทม์หรือทำการคำนวณ ru ti e เพื่อกำหนดประเภทของ a ตัวแปรหรือเพียงแค่เรียกใช้การconstexprคำนวณของคุณใหม่อย่างไร้จุดหมายจากความชั่วร้ายที่แท้จริง สามารถรอ 1 วินาทีต่ออักขระในบรรทัดของแหล่งที่มาที่กำหนดหรือใช้บรรทัดของแหล่งที่มาและใช้เพื่อกำหนดตำแหน่งหมากรุกจากนั้นเล่นทั้งสองฝ่ายเพื่อตัดสินว่าใครชนะ
Yakk - Adam Nevraumont

27

เป็นเรื่องง่ายมากที่จะทราบว่าการเรียกใช้constexprฟังก์ชันทำให้เกิดนิพจน์คงที่หลักหรือเป็นเพียงการปรับให้เหมาะสม:

ใช้ในบริบทที่ต้องใช้นิพจน์คงที่

int main()
{
    constexpr int test_const = length("abcd");
    std::array<char,length("abcdefgh")> test_const2;
}

4
... และคอมไพล์ด้วย-pedanticถ้าคุณใช้ gcc มิฉะนั้นคุณจะไม่ได้รับคำเตือนและข้อผิดพลาด
BЈовић

@ BЈовићหรือใช้ในบริบทที่ GCC ไม่มีส่วนขยายที่อาจเข้ามารบกวนเช่นอาร์กิวเมนต์เทมเพลต
Angew ไม่ภูมิใจอีกต่อไปเมื่อ

การแฮ็ก enumจะน่าเชื่อถือกว่าหรือไม่? เช่นenum { Whatever = length("str") }?
sharptooth

18
ที่น่ากล่าวถึงคือstatic_assert(length("str") == 3, "");
chris

8
constexpr auto test = /*...*/;อาจเป็นเรื่องทั่วไปและตรงไปตรงมาที่สุด
TC

20

เพียงแค่ทราบว่าคอมไพเลอร์ที่ทันสมัย (เช่น GCC-4.x) ทำstrlenสำหรับตัวอักษรของสตริงที่รวบรวมเวลาเพราะมันถูกกำหนดไว้ตามปกติเป็นฟังก์ชั่นที่แท้จริง โดยไม่ได้เปิดใช้งานการเพิ่มประสิทธิภาพ แม้ว่าผลลัพธ์จะไม่ใช่ค่าคงที่ของเวลาคอมไพล์

เช่น:

printf("%zu\n", strlen("abc"));

ผลลัพธ์ใน:

movl    $3, %esi    # strlen("abc")
movl    $.LC0, %edi # "%zu\n"
movl    $0, %eax
call    printf

หมายเหตุสิ่งนี้ใช้งานได้เนื่องจากstrlenเป็นฟังก์ชันในตัวหากเราใช้-fno-builtinsมันจะย้อนกลับไปเรียกมันในเวลาทำงานโปรดดูสด
Shafik Yaghmour

strlenคือconstexprสำหรับผมแม้จะมี-fno-nonansi-builtins(ดูเหมือนว่า-fno-builtinsไม่อยู่ในกรัม ++ ใด ๆ เพิ่มเติม) ฉันพูดว่า "constexpr" เพราะฉันทำได้template<int> void foo();และfoo<strlen("hi")>(); g ++ - 4.8.4
Aaron McDaid

19

ผมขอเสนอฟังก์ชันอื่นที่คำนวณความยาวของสตริงในเวลาคอมไพล์โดยไม่ต้องวนซ้ำ

template< size_t N >
constexpr size_t length( char const (&)[N] )
{
  return N-1;
}

มีลักษณะที่นี้โค้ดตัวอย่างที่ ideone


5
อาจไม่เท่ากับ strlen เนื่องจากฝัง '\ 0': strlen ("hi \ 0there")! = length ("hi \ 0there")
unkulunkulu

นี่เป็นวิธีที่ถูกต้องนี่คือตัวอย่างใน Effective Modern C ++ (ถ้าจำไม่ผิด) อย่างไรก็ตามมีคลาสสตริงที่ดีที่เป็น constexpr ทั้งหมดดูคำตอบนี้: str_const ของ Scott Schurrบางทีนี่อาจเป็นประโยชน์มากกว่า (และสไตล์ C น้อยกว่า)
QuantumKarl


now yow do: char temp[256]; sprintf(temp, "%u", 2); if(1 != length(temp)) printf("Your solution doesn't work"); ideone.com/IfKUHV
Pablo Ariel

7

ไม่มีการรับประกันว่าconstexprฟังก์ชันจะได้รับการประเมินในเวลาคอมไพล์แม้ว่าคอมไพเลอร์ที่สมเหตุสมผลจะเปิดใช้งานในระดับการเพิ่มประสิทธิภาพที่เหมาะสม ในทางกลับกันพารามิเตอร์เทมเพลตต้องได้รับการประเมินในเวลาคอมไพล์

ฉันใช้เคล็ดลับต่อไปนี้เพื่อบังคับให้ประเมินผลในเวลารวบรวม น่าเสียดายที่มันใช้งานได้เฉพาะกับค่าอินทิกรัลเท่านั้น (เช่นไม่ใช่ค่าทศนิยม)

template<typename T, T V>
struct static_eval
{
  static constexpr T value = V;
};

ตอนนี้ถ้าคุณเขียน

if (static_eval<int, length("hello, world")>::value > 7) { ... }

คุณสามารถมั่นใจได้ว่าifคำสั่งเป็นค่าคงที่เวลาคอมไพล์โดยไม่มีค่าใช้จ่ายในการรันไทม์


8
หรือใช้ std :: integral_constant <int, length (... )> :: value
Mircea Ispas

1
ตัวอย่างนี้เป็นการใช้งานที่ไร้จุดหมายเล็กน้อยเนื่องจากค่าlenความconstexprหมายlengthจะต้องได้รับการประเมินในเวลาคอมไพล์อยู่ดี
chris

@chris ฉันไม่รู้ว่ามันต้องเป็นแม้ว่าฉันจะสังเกตเห็นว่ามันอยู่กับคอมไพเลอร์ของฉัน
5gon12eder

โอเคตามคำตอบอื่น ๆ ส่วนใหญ่ฉันจึงแก้ไขตัวอย่างให้ไม่มีจุดหมายน้อยลง ในความเป็นจริงมันเป็นif-condition (ซึ่งเป็นสิ่งสำคัญที่คอมไพเลอร์ทำการกำจัดโค้ดที่ตายแล้ว) ซึ่งเดิมทีฉันใช้เคล็ดลับ
5gon12eder

1

คำอธิบายสั้น ๆ จากรายการของ Wikipedia เกี่ยวกับนิพจน์ค่าคงที่ทั่วไป :

การใช้ constexpr กับฟังก์ชันกำหนดข้อ จำกัด บางประการเกี่ยวกับสิ่งที่ฟังก์ชันนั้นสามารถทำได้ อันดับแรกฟังก์ชันต้องมีชนิดส่งคืนที่ไม่ใช่โมฆะ ประการที่สองเนื้อหาของฟังก์ชันไม่สามารถประกาศตัวแปรหรือกำหนดประเภทใหม่ได้ ประการที่สามเนื้อหาอาจมีเพียงการประกาศข้อความว่างและคำสั่งส่งคืนเดียว ต้องมีค่าอาร์กิวเมนต์ดังนั้นหลังจากการแทนที่อาร์กิวเมนต์นิพจน์ในคำสั่ง return จะสร้างนิพจน์คงที่

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


เงื่อนไขเหล่านี้ไม่รับประกันค่ากลับมาเป็นค่าคงที่ ตัวอย่างเช่นฟังก์ชันอาจถูกเรียกใช้ด้วยค่าอาร์กิวเมนต์อื่น ๆ
Ben Voigt

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