Chương trình được biên dịch khác nhau trong 3 trình biên dịch C ++ chính. Cái nào là đúng?


116

Là một phần tiếp theo thú vị (mặc dù không có tầm quan trọng thực tế lớn) cho câu hỏi trước đây của tôi: Tại sao C ++ cho phép chúng ta bao quanh tên biến trong ngoặc đơn khi khai báo một biến?

Tôi phát hiện ra rằng việc kết hợp khai báo trong ngoặc đơn với tính năng tên lớp được chèn có thể dẫn đến kết quả đáng ngạc nhiên về hành vi của trình biên dịch.

Hãy xem chương trình sau đây:

#include <iostream>
struct B
{
};

struct C
{
  C (){ std::cout << "C" << '\n'; }
  C (B *) { std::cout << "C (B *)" << '\n';}
};

B *y = nullptr;
int main()
{
  C::C (y);
}
  1. Biên dịch với g ++ 4.9.2 cho tôi lỗi biên dịch sau:

    main.cpp:16:10: error: cannot call constructor 'C::C' directly [-fpermissive]
  2. Nó biên dịch thành công với MSVC2013 / 2015 và in C (B *)

  3. Nó biên dịch thành công với clang 3.5 và in C

Vì vậy, câu hỏi bắt buộc là cái nào là đúng? :)

(Tôi mạnh mẽ lắc lư theo phiên bản clang và cách msvc dừng khai báo biến sau khi chỉ thay đổi kiểu với kỹ thuật typedef của nó có vẻ kỳ lạ)


3
C::C y;không có ý nghĩa, phải không? Cũng không C::C (y); Tại lần đầu tiên tôi nghĩ rằng đây là một thể hiện của Đa-gây nhiều tranh cãi-Parse stackoverflow.com/questions/tagged/most-vexing-parse , nhưng bây giờ tôi nghĩ rằng đó là hành vi không xác định chỉ có nghĩa là cả ba trình biên dịch là "đúng".
Dale Wilson

4
# 3 clang chắc chắn là sai, # 2 msvc quá dễ dãi và # 1 g ++ là đúng ((tôi đoán vậy)

8
C::Ckhông đặt tên kiểu mà nó đặt tên cho hàm, vì vậy GCC đúng imo.
Galik


Câu trả lời:


91

GCC là chính xác, ít nhất là theo quy tắc tra cứu C ++ 11. 3.4.3.1 [class.qual] / 2 chỉ định rằng, nếu trình xác định tên lồng nhau giống với tên lớp, thì nó đề cập đến hàm tạo không phải là tên lớp được chèn. Nó đưa ra ví dụ:

B::A ba;           // object of type A
A::A a;            // error, A::A is not a type name
struct A::A a2;    // object of type A

Dường như MSVC hiểu sai nó là biểu thức truyền kiểu hàm tạo tạm thời Cvới ytham số hàm tạo; và Clang giải thích sai nó như là một tuyên bố của một biến được gọi là ykiểu C.


2
Có, 3.4.3.1/2 là chìa khóa. Làm tốt lắm!
Các cuộc đua nhẹ nhàng trong quỹ đạo

Nó nói "Trong một tra cứu trong đó tên hàm không bị bỏ qua". Dường như với tôi, trong các ví dụ đã cho, cụ thể A::A a;, các tên hàm nên được bỏ qua - hay không?
Columbo

1
Đi theo cách đánh số trong N4296, khóa thực sự là 3.4.3.1/2.1: "nếu tên được chỉ định sau trình xác định tên lồng nhau, khi được tra cứu trong C, là tên lớp được chèn của C [...] thay vào đó, tên được coi là tên của hàm tạo của lớp C. " Tóm tắt của Mike là một chút đơn giản hóa mặc dù - ví dụ, một tên được đặt tên của lớp trong lớp sẽ cho phép một trình xác định tên lồng nhau khác với tên lớp vẫn tham chiếu đến tên lớp, vì vậy nó vẫn đề cập đến ctor.
Jerry Coffin

2
@Mgetz: Từ câu hỏi: "Nó biên dịch thành công với MSVC2013 / 2015 và bản in C (B *)" .
Các cuộc đua nhẹ nhàng trong quỹ đạo

2
Để hoàn thiện, điều này cần làm rõ liệu nó có hình thành không cần thiết với chẩn đoán, hoặc hình thành không có chẩn đoán cần thiết. Nếu sau này tất cả các trình biên dịch là "đúng".
MM

16

G ++ là chính xác vì nó đưa ra một lỗi. Bởi vì hàm tạo không thể được gọi trực tiếp theo định dạng như vậy mà không có newtoán tử. Và mặc dù mã của bạn gọi C::C, nó trông giống như một cuộc gọi constructor. Tuy nhiên, theo tiêu chuẩn 3.4.3.1 của C ++, đây không phải là một cuộc gọi chức năng hợp pháp hoặc tên loại ( xem câu trả lời của Mike Seymour ).

Clang là sai vì nó thậm chí không gọi đúng chức năng.

MSVC là một cái gì đó hợp lý, nhưng nó vẫn không theo tiêu chuẩn.


2
Làm những gì newthay đổi điều hành?
Neil Kirk

1
@NeilKirk: Rất nhiều, đối với những người nghĩ rằng đó new B(1,2,3)là một loại "cuộc gọi xây dựng trực tiếp" (tất nhiên, nó không) khác biệt với việc khởi tạo tạm thời B(1,2,3)hoặc khai báo B b(1,2,3).
Các cuộc đua nhẹ nhàng trong quỹ đạo

@LightningRacisinObrit Bạn mô tả thế nào new B(1,2,3)là?
dùng2030677

1
@ user2030677: Biểu thức mới, sử dụng từ khóa new, tên loại và danh sách đối số hàm tạo. Nó vẫn không phải là một "cuộc gọi xây dựng trực tiếp".
Các cuộc đua nhẹ nhàng trong quỹ đạo

"Clang sai vì nó thậm chí không gọi đúng hàm.": Tôi nghĩ (bởi vì nhận xét của OP về dấu ngoặc đơn trong khai báo) mà Clang diễn giải C::C (y);C::C y;, nghĩa là một biến của biến y loại C (sử dụng loại C được tiêm: : C trong khi bỏ qua lỗi đặc tả ngôn ngữ ngày càng điên rồ 3,4.1,2, điều này làm cho C :: C trở thành nhà xây dựng). Đó không phải là một lỗi rõ ràng như bạn nghĩ, imo.
Peter - Hồi phục lại
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.