2016-05-27 20 views
8

Gần đây tôi đã đọc ở đâu đó (không thể nhớ vị trí) về cách sử dụng dấu ngoặc để cho phép nhiều chuyển đổi do người dùng xác định, nhưng dường như có sự khác biệt giữa chuyển đổi theo hàm tạo và chuyển đổi theo phương pháp chuyển đổi Tôi không hiểu.Chuyển đổi ngầm C++ với bộ khởi tạo ngoặc đơn

xem xét:

#include <string> 

using ::std::string; 

struct C { 
    C() {} 
}; 

struct A { 
    A(const string& s) {} // Make std::string convertible to A. 
    operator C() const { return C(); } // Makes A convertible to C. 
}; 

struct B { 
    B() {} 
    B(const A& a) {} // Makes A convertible to B. 
}; 

int main() { 
    B b; 
    C c; 

    // This works. 
    // Conversion chain (all thru ctors): char* -> string -> A -> B 
    b = {{"char *"}}; 

    // These two attempts to make the final conversion through A's 
    // conversion method yield compiler errors. 
    c = {{"char *"}}; 
    c = {{{"char *"}}}; 
    // On the other hand, this does work (not surprisingly). 
    c = A{"char *"}; 
} 

Bây giờ, tôi có thể bị hiểu sai những gì trình biên dịch được thực hiện, nhưng (dựa trên các thử nghiệm trên và bổ sung) có vẻ như với tôi rằng nó không xem xét chuyển đổi bằng cách chuyển đổi-method. Đọc qua các phần 4 và 13.3.3.1 của tiêu chuẩn, tuy nhiên, tôi đã không thể tìm thấy một đầu mối tại sao lại như vậy. Giải thích là gì?

Cập nhật

Đây là một hiện tượng thú vị, tôi muốn giải thích. Nếu tôi thêm

struct D { 
    void operator<<(const B& b) {} 
}; 

và trong main:

D d; 
    d << {{ "char *" }}; 

tôi nhận được một lỗi, nhưng nếu thay vào đó tôi viết d.operator<<({{ "char *" }}); nó hoạt động tốt.

Cập nhật 2

Hình như mục 8.5.4 trong tiêu chuẩn có thể giữ một số câu trả lời. Tôi sẽ báo cáo những phát hiện của tôi.

+0

Một khởi tạo sử dụng các nhà xây dựng và sẽ không sử dụng toán tử chuyển đổi của một loại trung gian. Hai ví dụ không làm việc thất bại vì ngầm xây dựng một 'A' để sử dụng toán tử' 'của nó đi ngược lại với điều này. – Peter

+0

Liên quan: http://stackoverflow.com/questions/35790664/why-cant-i-implicitly-construct-an-object-given-a-suitable-constructor-when-pas/35790867#35790867 –

+0

Peter, những gì tôi ' m cố gắng hiểu là những gì các quy tắc là chính xác. Nếu tôi viết 'c = A {...' hoặc 'c = {A {...' nó hoạt động tốt thông qua phương thức chuyển đổi. Tại sao nó quyết định chỉ sử dụng ctors nếu tôi thả 'A'? – Ari

Trả lời

5

Có thể chuyển đổi một người dùng.

Trong b = {{"char *"}};

chúng tôi thực sự làm

b = B{{"char*"}}; // B has constructor with A (and a copy constructor not viable here) 

nên

b = B{A{"char*"}}; // One implicit conversion const char* -> std::string 

trong c = {{"const char*"}}, chúng tôi cố gắng

c = C{{"char *"}}; // but nothing to construct here. 
+0

Vậy tại sao c = {A {{"char *"}}}; công việc? – Ari

+1

@Ari: nó sẽ làm 'c = C {A {std :: chuỗi {" char * "}}}'. chỉ có ''''>' C' chuyển đổi. – Jarod42

+0

Nhưng đó là câu hỏi, phải không? Tại sao điều này không xảy ra khi 'A' bị bỏ? Tôi nghĩ rằng bạn đang nói rằng 'C {{' không hoạt động vì 'C' không có một ctor mà có' A' như một đối số. Rõ ràng, 'C {A' hoặc chỉ' {A' không xây dựng, mà là chuyển đổi qua phương thức 'A'. Tại sao điều này không xảy ra mà không nói 'A'? – Ari

0

Đào thông qua Phần 8 .5.4 của tiêu chuẩn và theo các tham chiếu chéo khác nhau trong đó, tôi nghĩ rằng tôi hiểu những gì đang xảy ra. Tất nhiên, IANAL, vì vậy tôi có thể sai; đây là nỗ lực tốt nhất của tôi.

Cập nhật: Phiên bản trước của câu trả lời thực sự đã sử dụng nhiều chuyển đổi. Tôi đã cập nhật nó để phản ánh sự hiểu biết hiện tại của tôi.

Chìa khóa để làm sáng tỏ mớ hỗn độn là thực tế rằng danh sách bắt đầu bằng dấu ngoặc đơn là không phải là một biểu thức (cũng giải thích tại sao d << {{"char *"}} sẽ không biên dịch). Cú pháp đặc biệt là gì, được điều chỉnh bởi các quy tắc đặc biệt, được cho phép trong một số ngữ cảnh cụ thể. Trong các ngữ cảnh này, các bối cảnh liên quan cho cuộc thảo luận của chúng ta là: các phép gán, đối số trong một cuộc gọi hàm và đối số trong một lời gọi hàm tạo.

Vậy điều gì sẽ xảy ra khi trình biên dịch thấy b = {{"char *"}}? Đây là trường hợp của bài tập.Nguyên tắc áp dụng là:

Một chuẩn bị tinh thần-init-list có thể xuất hiện ở phía bên tay phải của ... chuyển nhượng được xác định bởi một toán tử gán người dùng định nghĩa, trong trường hợp danh sách initializer được truyền làm đối số cho hàm toán tử.

(Có lẽ, toán tử gán bản mặc định được coi là một người dùng xác định nhiệm vụ điều hành. Tôi không thể tìm thấy một định nghĩa của thuật ngữ mà bất cứ nơi nào, và có vẻ như không được bất kỳ ngôn ngữ cho phép nẹp cú pháp đặc biệt để gán bản sao mặc định.)

Vì vậy, chúng tôi được giảm đối số chuyển sang toán tử gán bản sao mặc định B::operator=(const B&), trong đó đối số được chuyển là {{"char *"}}. Bởi vì một danh sách-init-brace không phải là một biểu thức, không có vấn đề chuyển đổi ở đây, mà là một hình thức khởi tạo tạm thời kiểu B, cụ thể, được gọi là khởi tạo danh sách.

Nếu không có constructor khởi tạo danh sách khả thi được tìm thấy, độ phân giải quá tải được thực hiện một lần nữa, nơi chức năng ứng cử viên đều là các nhà thầu của T lớp và danh sách đối số bao gồm các yếu tố của danh sách khởi tạo.

Vì vậy trình biên dịch sẽ loại bỏ cặp dấu ngoặc ngoài và thực hiện độ phân giải quá tải bằng cách sử dụng {"char *"} làm đối số. Điều này thành công, khớp với hàm tạo B::B(const A&) vì có một lần nữa liệt kê khởi tạo tạm thời loại A trong đó độ phân giải quá tải thành công để đối số A::A(const string&) cho đối số "char *", có thể thông qua một chuyển đổi do người dùng xác định, cụ thể là từ char* đến string .

Bây giờ, trong trường hợp c = {{"char *"}} quá trình này là tương tự, nhưng khi chúng tôi cố gắng liệt kê-khởi tạo tạm thời loại C với {{"char *"}}, độ phân giải quá tải không tìm được hàm tạo khớp. Vấn đề là do định nghĩa danh sách-khởi tạo chỉ hoạt động thông qua một hàm tạo có danh sách tham số có thể được thực hiện để phù hợp với nội dung của danh sách.

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