Có phải mọi người dùng bình thường và sử dụng các nghĩa đen do người dùng định nghĩa không xác định?


8

Chữ được xác định bởi người dùng phải bắt đầu bằng dấu gạch dưới.

Đây là một quy tắc phổ biến ít nhiều phổ biến mà bạn có thể tìm thấy trên mọi trang web có từ ngữ giáo dân nói về nghĩa đen của người dùng. Đó cũng là một quy tắc mà tôi (và có thể là những người khác?) Đã ngang nhiên bỏ qua kể từ khi có căn cứ "thật là nhảm nhí". Tất nhiên, điều đó hoàn toàn không đúng. Theo nghĩa chặt chẽ nhất, điều này sử dụng một định danh dành riêng, và do đó gọi ra Hành vi không xác định (mặc dù thực tế bạn không nhận được nhiều như một nhún vai từ trình biên dịch).

Vì vậy, suy nghĩ xem tôi có nên tiếp tục cố tình bỏ qua phần đó (theo ý kiến ​​của tôi là vô dụng) của tiêu chuẩn hay không, tôi quyết định xem xét những gì thực sự được viết. Bởi vì, bạn biết đấy, nó quan trọng những gì mọi người biết . Điều quan trọng là những gì được viết trong tiêu chuẩn.

[over.literal]nói rằng "một số" định danh hậu tố theo nghĩa đen được bảo lưu, liên kết đến [usrlit.suffix]. Cái sau nói rằng tất cả đều được bảo lưu, ngoại trừ những cái bắt đầu bằng dấu gạch dưới. OK, vì vậy đó chính xác là những gì chúng ta đã biết, được viết rõ ràng (hay đúng hơn là viết ngược).

Ngoài ra, [over.literal]có chứa một ghi chú gợi ý cho một điều rõ ràng nhưng rắc rối:

ngoại trừ các ràng buộc được mô tả ở trên, chúng là các hàm phạm vi không gian tên thông thường và các mẫu hàm

Chà, chắc chắn là như vậy. Không nơi nào nói rằng họ không, vì vậy bạn sẽ mong đợi điều gì khác.

Nhưng đợi một lát. [lex.name]tuyên bố rõ ràng rằng mỗi định danh bắt đầu bằng dấu gạch dưới trong không gian tên toàn cục được bảo lưu.

Bây giờ, một toán tử theo nghĩa đen thường, trừ khi bạn rõ ràng đặt nó vào một không gian tên (mà tôi tin là không có ai!?) Rất nhiều trong không gian tên toàn cầu. Vì vậy, tên, phải bắt đầu bằng dấu gạch dưới, được bảo lưu. Không có đề cập đến một ngoại lệ đặc biệt. Vì vậy, mỗi tên (có dấu gạch dưới hoặc không có) là một tên dành riêng.

Bạn có thực sự mong đợi đặt chữ theo định nghĩa của người dùng vào một không gian tên vì cách sử dụng "bình thường" (gạch dưới hay không) đang sử dụng tên dành riêng?


1
Tôi tự hỏi nếu hậu tố UDL được tính là một định danh.
HolyBlackCat

1
FWIW mã của bạn phải ở trong một không gian tên và nếu bạn tuân theo rằng bạn an toàn.
NathanOliver

@ NathanOliver-ReinstateMonica: Làm thế nào tôi thậm chí sẽ sử dụng nghĩa đen đó? Hãy nói rằng tôi đã đặt, bất cứ điều gì, nói ... _km(cho km) nó trong không gian tên udl. Sau đó, một nghĩa đen cho 5km trông như ... 5udl::_km?
Damon

@ NathanOliver-RebstateMonica Đó là những gì tôi nghĩ rằng nhưng điều đó không đúng, hãy xem câu trả lời của tôi.
Konrad Rudolph

1
@Damon Đó là những gì usingtuyên bố dành cho. Trong phạm vi mà bạn cần sử dụng bằng chữ, có một câu lệnh sử dụng nhập nó.
NathanOliver

Câu trả lời:


6

Có: sự kết hợp của việc cấm sử dụng _như là sự khởi đầu của một định danh toàn cầu cùng với việc yêu cầu các UDL không chuẩn bắt đầu với _nghĩa là bạn không thể đặt chúng vào không gian tên toàn cầu. Nhưng bạn không nên làm bẩn không gian tên toàn cầu bằng các công cụ, đặc biệt là UDL, do đó không phải là vấn đề lớn.

Thành ngữ truyền thống, như được sử dụng bởi tiêu chuẩn, là đặt UDL vào một literalskhông gian tên (và nếu bạn có các bộ UDL khác nhau, thì bạn đặt chúng ở khác inline namespacesbên dưới không gian tên đó). Không literalsgian tên đó thường nằm bên dưới cái chính của bạn. Khi bạn muốn sử dụng một bộ UDL cụ thể, bạn gọi using namespace my_namespace::literalshoặc bất kỳ không gian tên phụ nào chứa tập hợp lựa chọn theo nghĩa đen của bạn.

Điều này rất quan trọng vì UDL có xu hướng được viết tắt nhiều . Tiêu chuẩn ví dụ sử dụng scho std::string, nhưng cũng std::chrono::durationtrong vài giây. Mặc dù chúng áp dụng cho các loại chữ khác nhau ( sáp dụng cho chuỗi là một chuỗi, trong khi sáp dụng cho số là thời lượng), đôi khi có thể gây nhầm lẫn khi đọc mã sử dụng chữ viết tắt. Vì vậy, bạn không nên ném chữ vào tất cả người dùng thư viện của bạn; họ nên chọn sử dụng chúng.

Bằng cách sử dụng các không gian tên khác nhau cho các ( std::literals::string_literalsstd::literals::chrono_literals) này, người dùng có thể biết trước về bộ chữ mà họ muốn trong phần mã nào.


1
Thật không may, câu trả lời này dường như không giải quyết được tính hợp lệ của _Foocác hậu tố, đã bị bỏ qua khỏi câu hỏi nhưng khá khó hiểu.
Konrad Rudolph

3

Đây là một câu hỏi hay và tôi không chắc về câu trả lời, nhưng tôi nghĩ câu trả lời là "không, không phải là UB" dựa trên cách đọc tiêu chuẩn cụ thể.

[lex.name] /3.2 đọc:

Mỗi mã định danh bắt đầu bằng dấu gạch dưới được dành riêng cho việc triển khai để sử dụng làm tên trong không gian tên toàn cục.

Bây giờ, rõ ràng, hạn chế "như một tên trong không gian tên toàn cầu" nên được đọc là áp dụng cho toàn bộ quy tắc, không chỉ là cách triển khai có thể sử dụng tên đó. Đó là, ý nghĩa của nó là không

"mỗi mã định danh bắt đầu bằng dấu gạch dưới được dành riêng cho việc triển khai VÀ việc triển khai có thể sử dụng các mã định danh đó làm tên trong không gian tên toàn cầu"

nhưng đúng hơn

"Việc sử dụng bất kỳ mã định danh nào bắt đầu bằng dấu gạch dưới làm tên trong không gian tên toàn cục được dành riêng cho việc triển khai".

(Nếu chúng tôi tin cách giải thích đầu tiên, thì điều đó có nghĩa là không ai có thể khai báo một hàm được gọi my_namespace::_foo, chẳng hạn.)

Theo cách hiểu thứ hai, một cái gì đó giống như một tuyên bố toàn cầu operator""_foo(trong phạm vi toàn cầu) là hợp pháp, bởi vì một tuyên bố như vậy không sử dụng _foonhư một tên. Thay vào đó, định danh chỉ là một phần của tên thật, đó là operator""_foo( không bắt đầu bằng dấu gạch dưới).


Tôi sẽ đi với một giải thích tương tự. Xem xét một người có thể định nghĩa một chức năng như void operator+(foo, bar), trong đó rõ ràng tên chức năng không phải là một định danh, nhưng đó là một tên. Tương tự như vậy operator "" _foolà tên trong trường hợp của chúng tôi.
Người kể chuyện - Unslander Monica

2

Có phải mọi người dùng bình thường và sử dụng các nghĩa đen do người dùng định nghĩa không xác định?

Rõ ràng không.

Sau đây là cách sử dụng UDLs thành ngữ (và do đó chắc chắn là Bình thường) và nó được xác định rõ theo quy tắc bạn vừa liệt kê:

namespace si {
    struct metre {  };

    constexpr metre operator ""_m(long double value) { return metre{value}; }
}

Bạn đã liệt kê các trường hợp có vấn đề và tôi đồng ý với đánh giá của bạn về tính hợp lệ của chúng nhưng chúng dễ dàng tránh được trong mã C ++ thành ngữ nên tôi hoàn toàn không thấy vấn đề với từ ngữ hiện tại, ngay cả khi nó có khả năng vô tình.

Theo ví dụ trong [over.literal] / 8, chúng ta thậm chí có thể sử dụng chữ in hoa sau dấu gạch dưới:

float operator ""E(const char*);    // error: reserved literal suffix (20.5.4.3.5, 5.13.8)
double operator""_Bq(long double);  // OK: does not use the reserved identifier _Bq (5.10)
double operator"" _Bq(long double); // uses the reserved identifier _Bq (5.10)

Do đó, vấn đề duy nhất có vẻ là thực tế là tiêu chuẩn làm cho khoảng trắng giữa ""và tên UDL trở nên quan trọng.


Bắt tốt, tôi thậm chí không nghĩ về các đơn vị SI chữ in hoa.
Damon

1
Tiêu chuẩn chứa một ví dụ cho thấy operator""_Bqlà ok (có nghĩa operator""_Klà cũng ok). Bí quyết là bỏ qua khoảng trắng giữa ""và hậu tố. Xem C ++ 17 [over.literal] / 8.
Brian

@Brian Tôi ghét nó khi các ví dụ là nguồn duy nhất của từ ngữ quy phạm. Tuyệt vời tìm thấy. Và việc họ quyết định làm cho khoảng trắng này có ý nghĩa thậm chí còn rối tung hơn. Dù bằng cách nào, tôi đã sửa câu trả lời của mình.
Konrad Rudolph

Đó không phải là ví dụ là nguồn duy nhất. Thay vào đó, điều này xuất phát từ thực tế là một chuỗi ký tự do người dùng xác định là một mã thông báo tiền xử lý duy nhất. Điều đó đang được nói, ví dụ này làm rõ ý định, cộng với tôi sẽ không bao giờ nhận ra điều này nếu nó không phải là ví dụ.
Brian

1

Có, việc xác định người dùng của bạn được xác định bằng chữ trong không gian tên toàn cầu dẫn đến một chương trình không đúng định dạng.

Tôi đã không gặp phải điều này bởi vì tôi cố gắng tuân theo quy tắc:

Đừng đặt bất cứ thứ gì (bên cạnh main, không gian tên và extern "C"nội dung cho sự ổn định của ABI) trong không gian tên toàn cầu.

namespace Mine {
  struct meter { double value; };
  inline namespace literals {
    meter operator ""_m( double v ) { return {v}; }
  }
}

int main() {
  using namespace Mine::literals;
  std::cout << 15_m.value << "\n";
}

Điều này cũng có nghĩa là bạn không thể sử dụng _CAPSlàm tên theo nghĩa đen của mình, ngay cả trong một không gian tên.

Các không gian tên nội tuyến được gọi literalslà một cách tuyệt vời để đóng gói các toán tử theo nghĩa đen do người dùng xác định. Chúng có thể được nhập vào nơi bạn muốn sử dụng mà không cần phải đặt tên chính xác cho loại chữ nào bạn muốn hoặc nếu bạn nhập toàn bộ không gian tên, bạn cũng sẽ có được chữ.

Điều này tuân theo cách stdthư viện xử lý cả chữ, vì vậy, nên quen thuộc với người dùng mã của bạn.


0

Với nghĩa đen là hậu tố _X, ngữ pháp gọi _Xlà "định danh" .

Vì vậy, có: tiêu chuẩn, có lẽ vô tình, đã khiến không thể tạo UDT ở phạm vi toàn cầu, hoặc UDT bắt đầu bằng chữ in hoa, trong một chương trình được xác định rõ. (Lưu ý rằng trước đây không phải là điều bạn thường muốn làm!)

Điều này không thể được giải quyết về mặt biên tập: tên của các chữ do người dùng định nghĩa sẽ phải có "không gian tên" từ vựng riêng của chúng để ngăn xung đột với (ví dụ) tên của các hàm do triển khai thực hiện. Tuy nhiên, theo tôi, sẽ rất tốt nếu có một ghi chú không quy tắc ở đâu đó, chỉ ra hậu quả của các quy tắc này và chỉ ra rằng chúng có chủ ý.


Ngay cả với một ngoại lệ sẽ không thực hiện được xác định int _x(int);(toàn cầu) gây ra lỗi biên dịch?
Richard Critten 4/12/19

Có khả năng đọc thêm thú vị: CWG 1882
Các cuộc đua nhẹ nhàng trong quỹ đạo

1
@RichardCritten Ý bạn là gì?
Các cuộc đua nhẹ nhàng trong quỹ đạo

Tôi hiểu: "... có thể được miễn trừ ..." để chỉ ra rằng các toán tử do người dùng xác định của biểu mẫu _xnên được miễn [lex.name]. Nếu tôi đã hiểu lầm thì những gì tiếp theo là rác rưởi . Nếu việc triển khai đã khai báo một hàm int _x(int);trong phạm vi toàn cục (tên dành riêng như vậy là ok), thì người dùng đã khai báo long double operator "" _x(long double);(ví dụ) sẽ gặp lỗi biên dịch.
Richard Critten 4/12/19

Tôi không thấy làm thế nào miễn trừ có thể chữa vấn đề này.
Richard Critten 4/12/19
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.