Sự khác biệt của hai trường hợp constexpr của con trỏ __func__ vẫn là constexpr?


14

Đây có phải là C ++ hợp lệ không?

int main() {
    constexpr auto sz = __func__ - __func__;
    return sz;
}

GCC và MSVC nghĩ rằng nó ổn, Clang nghĩ rằng nó không phải: Trình biên dịch Explorer .


Tất cả các trình biên dịch đồng ý rằng cái này là OK: Trình biên dịch Explorer .

int main() {
    constexpr auto p = __func__;
    constexpr auto p2 = p;
    constexpr auto sz = p2 - p;
    return sz;
}

Clang một lần nữa không thích cái này, nhưng những cái khác thì ổn với nó: Compiler Explorer

int main() {
    constexpr auto p = __func__;
    constexpr auto p2 = __func__;
    constexpr auto sz = p2 - p;
    return sz;
}

Có chuyện gì ở đây vậy? Tôi nghĩ số học trên các con trỏ không liên quan là hành vi không xác định nhưng __func__trả về cùng một con trỏ, không? Tôi không chắc chắn, vì vậy tôi nghĩ rằng tôi có thể kiểm tra nó. Nếu tôi nhớ lại chính xác, std::equal_tocó thể so sánh các con trỏ không liên quan mà không có hành vi không xác định:

#include <functional>

int main() {
    constexpr std::equal_to<const char*> eq{};
    static_assert(eq(__func__, __func__));
}

Clang nghĩ rằng đó eq(__func__, __func__)không phải là một biểu thức không đổi, mặc dù std::equal_to::operator() là constexpr . Các trình biên dịch khác không phàn nàn: Trình biên dịch Explorer


Clang sẽ không biên dịch cái này. Khiếu nại __func__ == __func__không phải là biểu thức không đổi: Trình biên dịch Explorer

int main() {
    static_assert(__func__ == __func__);
}

Từ Function_def định , __func__là as-if static const char __func__[] = "function-name";và tương đương được chấp nhận Demo ...
Jarod42

Thật thú vị, nó hoạt động nếu bạn khởi tạo biến constexpr với __func__và sử dụng biến đó trong static_assert ...
florestan

@ Jarod42 Vậy đây là một lỗi trong Clang?
Ayxan

@florestan như thế này ? Nó sẽ không được biên dịch với Clang. Ví dụ thứ 2 và thứ 3 của tôi trong câu hỏi là cách bạn đề cập. Một biên dịch, một cái khác thì không.
Ayxan

1
Xem thêm CWG1962 , có thể loại bỏ __func__hoàn toàn khỏi đánh giá constexpr.
Davis Herring

Câu trả lời:


13

__func__trong C ++ là một định danh. Đặc biệt, nó tham chiếu một đối tượng cụ thể. Từ [dcl.fct.def.general] / 8 :

Biến được xác định trước hàm cục bộ _­_­func_­_­được định nghĩa như thể một định nghĩa của biểu mẫu

static const char __func__[] = "function-name";

đã được cung cấp, trong đó tên hàm là một chuỗi xác định thực hiện. Không xác định được liệu một biến như vậy có địa chỉ khác với địa chỉ của bất kỳ đối tượng nào khác trong chương trình hay không.

Là một biến được xác định trước chức năng cục bộ , định nghĩa này (như thể) xuất hiện ở đầu khối chức năng. Như vậy, bất kỳ việc sử dụng nào __func__trong khối đó sẽ đề cập đến biến đó.

Đối với phần "bất kỳ đối tượng khác", một biến xác định một đối tượng. __func__Đặt tên cho đối tượng được xác định bởi biến đó. Do đó, trong một hàm, tất cả các cách sử dụng __func__tên cùng một biến. Điều không xác định là liệu biến đó có phải là một đối tượng khác biệt với các đối tượng khác hay không.

Đó là, nếu bạn đang ở trong một hàm có tên foovà bạn đã sử dụng nghĩa đen "foo"ở một nơi khác trong vấn đề, thì việc thực thi có biến __func__cũng là cùng một đối tượng mà nghĩa đen "foo"trả về. Đó là, tiêu chuẩn không yêu cầu mọi chức năng __func__xuất hiện phải lưu trữ dữ liệu tách biệt với chính chuỗi ký tự.

Bây giờ, quy tắc "như thể" của C ++ cho phép các triển khai đi chệch khỏi điều này, nhưng họ không thể làm điều đó theo cách có thể phát hiện được. Vì vậy, trong khi chính biến đó có thể có hoặc không có địa chỉ riêng biệt với các đối tượng khác, việc sử dụng __func__trong cùng một chức năng phải hoạt động như thể chúng đang đề cập đến cùng một đối tượng.

Clang dường như không thực hiện __func__theo cách này. Nó xuất hiện để thực hiện nó như thể nó trả về một chuỗi giá trị bằng chữ của tên hàm. Hai chuỗi ký tự riêng biệt không phải đề cập đến cùng một đối tượng, vì vậy trừ các con trỏ cho chúng là UB. Và hành vi không xác định trong một bối cảnh biểu hiện không đổi được hình thành.

Điều duy nhất khiến tôi do dự khi nói rằng Clang sai 100% ở đây là [temp.arg.nontype] / 2 :

Đối với tham số mẫu không phải kiểu tham chiếu hoặc kiểu con trỏ, giá trị của biểu thức hằng không được tham chiếu (hoặc đối với loại con trỏ, sẽ không phải là địa chỉ của):

...

  • một _­_­func_­_biến được xác định trước .

Hãy xem, điều này dường như cho phép một số lỗi trong quá trình thực hiện. Đó là, __func__về mặt kỹ thuật có thể là một biểu thức không đổi, bạn không thể sử dụng nó trong một tham số mẫu. Nó được coi như một chuỗi ký tự, mặc dù về mặt kỹ thuật nó là một biến.

Vì vậy, ở một mức độ nào đó, tôi sẽ nói rằng tiêu chuẩn đang nói ra từ cả hai phía của miệng.


Vì vậy, nói đúng ra __func__có thể là một biểu thức không đổi trong tất cả các trường hợp trong câu hỏi của tôi, phải không? Vì vậy, mã nên đã được biên dịch.
Ayxan

Điều gì về "Không xác định được liệu một biến như vậy có địa chỉ khác với bất kỳ đối tượng nào khác trong chương trình hay không." phần? Hành vi không xác định có nghĩa là không xác định trong hành vi của máy trừu tượng. Điều đó có thể là vấn đề để đánh giá constexpr? Điều gì xảy ra nếu lần xuất hiện đầu tiên của __func__địa chỉ giống như đối tượng khác và trong lần xuất hiện thứ hai của địa __func__chỉ đó thì không? Cấp, điều đó không có nghĩa là địa chỉ khác nhau giữa hai trường hợp, nhưng tôi vẫn bối rối!
Julian Schaub - litb

@ JohannesSchaub-litb: " Thế còn" Không xác định được liệu một biến như vậy có địa chỉ khác với bất kỳ đối tượng nào khác trong chương trình hay không. "Còn về nó? __func__không phải là một vĩ mô; nó là một định danh đặt tên cho một biến cụ thể và do đó là một đối tượng cụ thể. Do đó, bất kỳ việc sử dụng __func__trong cùng một chức năng sẽ dẫn đến một giá trị tham chiếu đến cùng một đối tượng. Hay hơn nữa, nó không thể được thực hiện theo cách mà điều này sẽ không xảy ra.
Nicol Bolas

@Nicol đề cập đến cùng một đối tượng. Nhưng đối tượng đó có thể ngay lập tức có cùng địa chỉ với đối tượng khác. Và tại thiên đường khác ngay lập tức. Tôi không nói đó là một vấn đề, nhưng tôi chỉ nhắc nhở mọi người về khả năng này. Và sau tất cả, tôi cũng có thể bị nhầm lẫn, vì vậy tôi cũng nói điều này với hy vọng được sửa chữa hoặc được xác nhận.
Julian Schaub - litb

@ JohannesSchaub-litb: " Nhưng đối tượng đó có thể ngay lập tức có cùng địa chỉ với một đối tượng khác. " Điều đó không được phép theo mô hình đối tượng C ++. Hai đối tượng, không ai được lồng vào nhau, cả hai không thể ở trong vòng đời của chúng trong cùng một bộ lưu trữ cùng một lúc. Và đối tượng trong câu hỏi có thời lượng lưu trữ tĩnh, vì vậy trừ khi bạn sử dụng vị trí newtrên nó, nó sẽ không đi đến đâu cho đến khi chương trình kết thúc.
Nicol Bolas
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.