2017-11-04 16 views
22

Hãy xem xét hai loại sau đây:Gọi để điều hành chuyển đổi thay vì chuyển đổi constructor trong C++ 17 trong giải quyết tình trạng quá tải

#define PRETTY(x) (std::cout << __PRETTY_FUNCTION__ << " : " << (x) << '\n') 

struct D; 

struct C { 
    C() { PRETTY(this);} 
    C(const C&) { PRETTY(this);} 
    C(const D&) { PRETTY(this);} 
}; 

struct D { 
    D() { PRETTY(this);} 
    operator C() { PRETTY(this); return C();} 
}; 

Chúng tôi quan tâm trong việc giải quyết tình trạng quá tải giữa hai cấu trúc:

C::C(const C&); 
C::C(const D&); 

Mã này hoạt động như mong đợi:

void f(const C& c){ PRETTY(&c);} 
void f(const D& d){ PRETTY(&d);} 
/*--------*/ 
D d; 
f(d); //calls void f(const D& d) 

từ void f(const D& d) là kết quả phù hợp hơn.

Nhưng:

D d; 
C c(d); 

(như bạn có thể nhìn thấy here)

cuộc gọi D::operator C() khi biên dịch với std=c++17, và kêu gọi C::C(const D&) với std=c++14 trên cả hai kêu vang và ĐẦU gcc. Hơn nữa, hành vi này đã thay đổi gần đây: với clang 5, C::C(const D&) được gọi với cả hai std=c++17std=c++14.

Hành vi chính xác ở đây là gì?

đọc dự kiến ​​của tiêu chuẩn (dự thảo mới nhất N4687):

C c(d) là một trực khởi mà không phải là một sự bỏ bớt bản sao ([dcl.init] /17.6.1). [dcl.init] /17.6.2 cho chúng ta biết rằng các nhà thầu có thể áp dụng được liệt kê và cái tốt nhất được chọn bởi độ phân giải quá tải. [over.match.ctor] cho chúng ta biết rằng các nhà xây dựng có thể áp dụng được trong trường hợp này là tất cả các nhà xây dựng.

Trong trường hợp này: C(), C(const C&)C(const D&) (không di chuyển ctor). C() rõ ràng là không khả thi và do đó bị loại bỏ khỏi tập quá tải. ([over.match.viable])

Các nhà xây dựng không có tham số đối tượng ẩn và do đó, C(const C&)C(const D&) cả hai đều lấy chính xác một tham số. ([over.match.funcs]/2)

Bây giờ chúng tôi truy cập [over.match.best]. Ở đây chúng tôi thấy rằng chúng tôi cần xác định xem của hai trình tự chuyển đổi tiềm ẩn (ICS) nào tốt hơn. ICS của C(const D&) chỉ liên quan đến chuỗi chuyển đổi chuẩn, nhưng ICS của C(const C&) liên quan đến chuỗi chuyển đổi do người dùng xác định.

Do đó, C(const D&) phải được chọn thay vì C(const C&).


Điều thú vị là hai thay đổi này cả hai sẽ làm cho "đúng" constructor để được gọi là:

operator C() { /* */ } vào operator C() const { /* */ }

hoặc

C(const D&) { /* */ } vào C(D&) { /* */ }

Đây là những gì sẽ xảy ra (tôi nghĩ) trong bản sao trường hợp alization trong đó chuyển đổi do người dùng xác định và các nhà xây dựng chuyển đổi có thể bị quá tải độ phân giải .


Như Columbo khuyến Tôi nộp một báo cáo lỗi với gcc và kêu vang

gcc lỗi https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82840

kêu vang lỗi https://bugs.llvm.org/show_bug.cgi?id=35207

Trả lời

14

Core issue 243 (17 tuổi):

Có một vấn đề nghiêm trọng vừa phải với định nghĩa về tình trạng quá tảiĐộ phân giải. Hãy xem xét ví dụ này:

struct B; 
struct A { 
    A(B); 
}; 
struct B { 
    operator A(); 
} b; 
int main() { 
    (void)A(b); 
} 

Đây là định nghĩa khá "mơ hồ", đúng không? Bạn muốn chuyển đổi một B một A, và có hai cách đều tốt làm điều đó: một constructor của A mà phải mất một B, và một chức năng chuyển đổi của B mà trả về một A.

Những gì chúng tôi phát hiện khi chúng tôi theo dõi điều này thông qua tiêu chuẩn, không may là một nhà xây dựng được ưu tiên hơn chức năng chuyển đổi . Định nghĩa về khởi tạo trực tiếp (biểu mẫu được đánh dấu) của một lớp chỉ xem xét các hàm tạo của lớp đó. Trong trường hợp này, các nhà thầu là các nhà xây dựng A(B) và (được tạo ra ngầm) A(const A&) trình tạo bản sao. Đây là cách họ được xếp hạng trên các trận đấu tranh cãi:

  • A(B): kết hợp chính xác (cần một B, có một B)
  • A(const A&): người dùng xác định chuyển đổi (B::operator A dùng để chuyển đổi B-A)

Nói cách khác, chức năng chuyển đổi không được xem xét, nhưng nó hoạt động với, trong hiệu ứng , một điểm chấp của một người dùng xác định d chuyển đổi. Để đặt một cách khác nhau , vấn đề này là vấn đề về trọng số, không phải là vấn đề rằng một số đường dẫn chuyển đổi không được xem xét.[...]

Ghi chú từ 1/10 họp:

Nó chỉ ra rằng có thực hành hiện cả hai cách về vấn đề này, vì vậy nó không rõ ràng rằng đó là "bị hỏng". Có một số lý do để cảm thấy rằng một cái gì đó trông giống như một "cuộc gọi constructor" nên gọi một constructor nếu có thể, chứ không phải là một chức năng chuyển đổi. CWG quyết định để nó một mình.

Có vẻ như bạn đã đúng. Hơn nữa, Clang và GCC chọn toán tử chuyển đổi hầu như không phải là lựa chọn tốt nhất, không theo từ ngữ hay trực giác, vì vậy trừ khi điều này là do tính tương thích ngược (và thậm chí sau đó), một báo cáo lỗi sẽ là thích hợp.

+0

Nhận xét không dành cho thảo luận mở rộng; cuộc hội thoại này đã được [chuyển sang trò chuyện] (http://chat.stackoverflow.com/rooms/158716/discussion-on-answer-by-columbo-call-to-conversion-operator-instead-of-convertin). – Andy

Các vấn đề liên quan