Độ dài tính toán của một chuỗi C tại thời điểm biên dịch. Đây có thực sự là một constexpr?


94

Tôi đang cố gắng tính độ dài của một chuỗi ký tự tại thời điểm biên dịch. Để làm như vậy, tôi đang sử dụng mã sau:

#include <cstdio>

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

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

Mọi thứ hoạt động như mong đợi, chương trình in ra 4 và 8. Mã hợp ngữ được tạo bởi clang cho thấy rằng kết quả được tính toán tại thời điểm biên dịch:

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

Câu hỏi của tôi: nó có được đảm bảo bởi tiêu chuẩn rằng lengthchức năng sẽ được đánh giá thời gian biên dịch không?

Nếu điều này là đúng, cánh cửa để biên dịch chuỗi thời gian tính toán theo chuỗi nghĩa đen vừa mở ra cho tôi ... ví dụ: tôi có thể tính toán hàm băm tại thời điểm biên dịch và nhiều hơn nữa ...


3
Miễn là tham số là một biểu thức hằng, nó phải như vậy.
chris

1
@chris Có đảm bảo rằng thứ gì đó có thể là một biểu thức hằng số phải được đánh giá tại thời điểm biên dịch khi được sử dụng trong ngữ cảnh không yêu cầu một biểu thức hằng số không?
TC

12
BTW, bao gồm <cstdio>và sau đó gọi ::printflà không di động. Tiêu chuẩn chỉ yêu cầu <cstdio>cung cấp std::printf.
Ben Voigt

1
@BenVoigt Ok, cảm ơn đã chỉ ra rằng :) Ban đầu tôi đã sử dụng std :: cout, nhưng các mã được tạo khá lớn để tìm ra giá trị thực tế :)
Mircea Ispas

3
@Felics Tôi thường sử dụng chốt thần khi trả lời các câu hỏi liên quan đến việc tối ưu hóa và việc sử dụng printfcó thể dẫn đến việc xử lý ít mã hơn đáng kể.
Shafik Yaghmour

Câu trả lời:


76

Các biểu thức hằng số không được đảm bảo sẽ được đánh giá tại thời điểm biên dịch, chúng tôi chỉ có một trích dẫn không chuẩn từ phần chuẩn C ++ dự thảo 5.19 Biểu thức hằng số cho biết điều này mặc dù:

[...]> [Lưu ý: Biểu thức hằng số có thể được đánh giá trong quá trình dịch. — Ghi chú cuối]

Bạn có thể gán kết quả cho constexprbiến để đảm bảo nó được đánh giá tại thời điểm biên dịch, chúng ta có thể thấy điều này từ tham chiếu C ++ 11 của Bjarne Stroustrup có nội dung ( nhấn mạnh của tôi ):

Ngoài việc có thể đánh giá các biểu thức tại thời điểm biên dịch, chúng ta muốn có thể yêu cầu các biểu thức được đánh giá tại thời điểm biên dịch; constexpr đứng trước một định nghĩa biến thực hiện điều đó (và ngụ ý const):

Ví dụ:

constexpr int len1 = length("abcd") ;

Bjarne Stroustrup đưa ra một bản tóm tắt về thời điểm chúng tôi có thể đảm bảo đánh giá thời gian biên dịch trong mục blog isocpp này và nói:

[...] Câu trả lời đúng - như Herb đã nêu - là theo tiêu chuẩn, một hàm constexpr có thể được đánh giá tại thời gian trình biên dịch hoặc thời gian chạy trừ khi nó được sử dụng như một biểu thức hằng, trong trường hợp đó, nó phải được đánh giá tại thời gian biên dịch. -thời gian. Để đảm bảo đánh giá thời gian biên dịch, chúng ta phải sử dụng nó khi cần biểu thức hằng số (ví dụ: như một mảng bị ràng buộc hoặc như một nhãn trường hợp) hoặc sử dụng nó để khởi tạo một constexpr. Tôi hy vọng rằng không có trình biên dịch tự trọng nào bỏ lỡ cơ hội tối ưu hóa để thực hiện những gì tôi đã nói ban đầu: "Một hàm constexpr được đánh giá tại thời điểm biên dịch nếu tất cả các đối số của nó là biểu thức không đổi."

Vì vậy, điều này nêu ra hai trường hợp cần được đánh giá tại thời điểm biên dịch:

  1. Sử dụng nó khi cần có một biểu thức hằng số, điều này dường như có ở bất kỳ đâu trong tiêu chuẩn nháp nơi cụm từ shall be ... converted constant expressionhoặc shall be ... constant expressionđược sử dụng, chẳng hạn như một giới hạn mảng.
  2. Sử dụng nó để khởi tạo một constexprnhư tôi đã phác thảo ở trên.

4
Điều đó nói rằng, về nguyên tắc, trình biên dịch được quyền xem một đối tượng có liên kết nội bộ hoặc không có liên kết với constexpr int x = 5;, quan sát rằng nó không yêu cầu giá trị tại thời điểm biên dịch (giả sử nó không được sử dụng làm tham số mẫu hoặc không) và thực sự phát ra mã tính toán giá trị ban đầu trong thời gian chạy bằng cách sử dụng 5 giá trị tức thì của 1 và 4 hoạt động bổ sung. Một ví dụ thực tế hơn: trình biên dịch có thể đạt đến giới hạn đệ quy và trì hoãn tính toán cho đến thời gian chạy. Trừ khi bạn làm điều gì đó buộc trình biên dịch thực sự sử dụng giá trị, "đảm bảo được đánh giá tại thời điểm biên dịch" là một vấn đề QOI.
Steve Jessop

@SteveJessop Bjarne dường như đang sử dụng một khái niệm không có từ tương tự mà tôi có thể tìm thấy trong tiêu chuẩn dự thảo được sử dụng như một biểu thức không đổi có nghĩa là được đánh giá khi dịch. Vì vậy, có vẻ như tiêu chuẩn không nêu rõ ràng những gì anh ấy đang nói, vì vậy tôi có xu hướng đồng ý với bạn. Mặc dù cả Bjarne và Herb dường như đồng ý về điều này, nhưng điều đó có thể cho thấy nó chỉ là chưa được xác định.
Shafik Yaghmour

2
Tôi nghĩ rằng cả hai đều chỉ xem xét "trình biên dịch tự tôn trọng", trái ngược với trình biên dịch tuân theo tiêu chuẩn nhưng cố ý gây cản trở mà tôi đưa ra. Nó rất hữu ích như một phương tiện lý luận về những gì tiêu chuẩn thực sự đảm bảo , và không có nhiều khác ;-)
Steve Jessop

3
@SteveJessop Cố tình làm tắc nghẽn các trình biên dịch, như Hell ++ khét tiếng (và rất tiếc là không tồn tại). Một thứ như vậy thực sự sẽ rất tốt để kiểm tra tính phù hợp / tính di động.
Angew không còn tự hào về SO

Theo quy tắc as-if, ngay cả việc sử dụng giá trị làm hằng số thời gian biên dịch dường như là không đủ: trình biên dịch có thể tự do gửi bản sao nguồn của bạn và biên dịch lại nó trong thời gian chạy hoặc thực hiện tính toán để xác định loại biến, hoặc chỉ chạy lại constexprtính toán của bạn một cách vô nghĩa . Thậm chí có thể miễn phí chờ 1 giây cho mỗi ký tự trong một dòng nguồn nhất định hoặc lấy một dòng nguồn nhất định và sử dụng nó để gieo một vị trí cờ vua, sau đó chơi cả hai bên để xác định ai thắng.
Yakk - Adam Nevraumont

27

Thực sự dễ dàng để tìm hiểu xem một lệnh gọi constexprhàm dẫn đến một biểu thức hằng số cốt lõi hay chỉ đơn thuần là được tối ưu hóa:

Sử dụng nó trong ngữ cảnh yêu cầu biểu thức hằng.

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

4
... và biên dịch với -pedantic, nếu bạn sử dụng gcc. Nếu không, bạn sẽ không nhận được cảnh báo và lỗi nào
BЈовић

@ BЈовић Hoặc sử dụng nó trong ngữ cảnh mà GCC không có phần mở rộng nào có thể cản trở, chẳng hạn như đối số mẫu.
Angew không còn tự hào về SO

Liệu một cuộc tấn công enum có đáng tin cậy hơn không? Chẳng hạn như enum { Whatever = length("str") }?
sharptooth

18
Đáng được đề cập làstatic_assert(length("str") == 3, "");
chris

8
constexpr auto test = /*...*/;có lẽ là khái quát và đơn giản nhất.
TC

19

Chỉ cần lưu ý rằng các trình biên dịch hiện đại (như gcc-4.x) thực hiện strlenđối với các chuỗi ký tự tại thời điểm biên dịch vì nó thường được định nghĩa là một hàm nội tại . Không có tối ưu hóa nào được bật. Mặc dù kết quả không phải là hằng số thời gian biên dịch.

Ví dụ:

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

Kết quả trong:

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

Lưu ý, các công trình này vì strlenlà một built-in chức năng, nếu chúng ta sử dụng -fno-builtinsnó reverts để gọi đó là tại thời gian chạy, nhìn thấy nó sống
Shafik Yaghmour

strlenconstexprđối với tôi, ngay cả với -fno-nonansi-builtins(dường như -fno-builtinskhông tồn tại trong g ++ nữa). Tôi nói "constexpr", vì tôi có thể làm được điều này template<int> void foo();foo<strlen("hi")>(); g ++ - 4.8.4
Aaron McDaid

18

Hãy để tôi đề xuất một hàm khác tính toán độ dài của một chuỗi tại thời điểm biên dịch mà không cần đệ quy.

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

Hãy xem mã mẫu này tại Ideone .


4
Nó có thể không bằng strlen do được nhúng '\ 0': strlen ("hi \ 0there")! = Length ("hi \ 0there")
unaulunkulu

Đây là cách chính xác, đây là một ví dụ trong C ++ Hiện đại hiệu quả (nếu tôi nhớ lại chính xác). Tuy nhiên, có một lớp chuỗi tuyệt vời hoàn toàn là constexpr, hãy xem câu trả lời này: str_const của Scott Schurr , có lẽ điều này sẽ hữu ích hơn (và ít kiểu C hơn).
QuantumKarl

@MikeWeir Ops, điều đó thật kỳ lạ. Dưới đây là các liên kết khác nhau: liên kết đến câu hỏi , liên kết tới giấy , liên kết đến nguồn trên git
QuantumKarl

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

7

Không có gì đảm bảo rằng một constexprchức năng được đánh giá tại thời điểm biên dịch, mặc dù bất kỳ trình biên dịch hợp lý nào sẽ thực hiện nó ở các mức tối ưu hóa thích hợp được kích hoạt. Mặt khác, các tham số mẫu phải được đánh giá tại thời điểm biên dịch.

Tôi đã sử dụng thủ thuật sau để buộc đánh giá tại thời điểm biên dịch. Thật không may, nó chỉ hoạt động với các giá trị tích phân (tức là không hoạt động với các giá trị dấu phẩy động).

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

Bây giờ, nếu bạn viết

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

bạn có thể chắc chắn rằng ifcâu lệnh là một hằng số thời gian biên dịch mà không có chi phí thời gian chạy.


8
hoặc chỉ sử dụng std :: integral_constant <int, chiều dài (...)> :: trị
Mircea Ispas

1
Các ví dụ là một chút của một sử dụng vô nghĩa vì lenconstexprphương tiện lengthphải được đánh giá tại thời gian biên dịch anyway.
chris

@ Chris Tôi không biết nó phải được, mặc dù tôi đã quan sát thấy rằng nó với trình biên dịch của tôi.
5gon12eder

Ok, theo phần lớn các câu trả lời khác thì nó phải như vậy, vì vậy tôi đã sửa đổi ví dụ để bớt vô nghĩa hơn. Trên thực tế, đó là một ifđiều kiện (điều kiện cần thiết là trình biên dịch đã thực hiện loại bỏ mã chết) mà tôi đã sử dụng thủ thuật ban đầu.
5gon12eder

1

Giải thích ngắn gọn từ mục nhập Wikipedia về Biểu thức hằng số tổng quát :

Việc sử dụng constexpr trên một hàm đặt ra một số hạn chế về những gì hàm đó có thể làm. Đầu tiên, hàm phải có kiểu trả về không void. Thứ hai, thân hàm không thể khai báo biến hoặc định nghĩa kiểu mới. Thứ ba, phần thân chỉ có thể chứa các khai báo, câu lệnh null và một câu lệnh trả về duy nhất. Phải tồn tại các giá trị đối số sao cho sau khi thay thế đối số, biểu thức trong câu lệnh trả về tạo ra một biểu thức không đổi.

Việc có constexprtừ khóa trước định nghĩa hàm sẽ hướng dẫn trình biên dịch kiểm tra xem những giới hạn này có được đáp ứng hay không. Nếu có, và hàm được gọi với một hằng số, giá trị trả về được đảm bảo là không đổi và do đó có thể được sử dụng ở bất cứ nơi nào yêu cầu biểu thức hằng số.


Các điều kiện này không đảm bảo giá trị trả về là không đổi . Ví dụ, hàm có thể được gọi với các giá trị đối số khác.
Ben Voigt

Đúng vậy, @BenVoigt. Tôi đã chỉnh sửa nó để được gọi với một biểu thức không đổi.
kaedinger,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.