2016-12-20 27 views
7

Tôi có mã sau sẽ không biên dịch, phàn nàn rằng toán tử + = không tồn tại. + = Điều hành đang ở đây tuyên bố bên ngoài của lớp A.Sự khác biệt giữa toán tử C++ quá tải bên trong và bên ngoài

template < typename _T > 
class A { 
public: 
    operator _T() const { return 42 ; } 
}; 

template <typename _T > 
A<_T> & operator += (A<_T> & l, _T r) { return l ; } 


int main() { 
    A<int> e, f ; 
    e += f ; 
    return 0 ; 
} 

Tuy nhiên, nếu tôi thực hiện các nhà điều hành bên trong lớp A, mã biên dịch và các công trình:

template < typename _T > 
class A { 
public: 
    operator _T() const { return 42 ; } 

    A<_T> & operator += (_T r) { return *this ; } 
}; 


int main() { 
    A<int> e, f ; 
    e += f ; 
    return 0 ; 
} 

sự khác biệt giữa hai mã này là gì? Họ không phải là tương đương?

Điều này được biên dịch bằng gcc 4.4.7-4.

+2

Đây không phải là vấn đề, nhưng tên bắt đầu bằng dấu gạch dưới theo sau là một chữ cái viết hoa ('_T') được dành riêng để sử dụng bởi việc triển khai. Không sử dụng chúng trong mã của bạn. –

Trả lời

6

Ví dụ đầu tiên thất bại trong việc biên dịch vì khấu trừ mẫu đối không làm bất kỳ chuyển đổi. Với

template <typename _T > 
A<_T> & operator += (A<_T> & l, _T r) { return l ; } 

Cả lr góp phần xác định _T là gì. Khi bạn thực hiện e += f thì trình biên dịch nhận được rằng _T phải là int cho l và được r phải là A<int> vì đó là loại f. Vì chúng không khớp với nó nên không biên dịch được.

Trong mã thứ hai không có khoản khấu trừ đối số mẫu nào đang diễn ra. Trình biên dịch biết những gì _T là từ sự khởi tạo của lớp vì vậy tất cả nó cần để có thể làm là chuyển đổi những gì được chuyển đến r đến một _T.


Tôi cũng khuyên bạn nên thoát khỏi thói quen bắt đầu tên có dấu gạch dưới. Có một loạt các quy tắc về họ và nếu bạn vi phạm chúng thì chương trình của bạn có hành vi không xác định vì chúng được dành riêng cho việc triển khai. Để xem thêm, hãy xem What are the rules about using an underscore in a C++ identifier?

1

Chúng không phải là tương đương?

Không, bởi vì trong ví dụ đầu tiên, _T là một mẫu function parameter which gets deduced, trong khi trong ví dụ thứ hai nó đã được biết.

Hãy tưởng tượng ví dụ thứ hai được mở rộng bởi trình biên dịch:

template <> 
class A_int { 
public: 
    operator int() const { return 42 ; } 

    A<int> & operator += (int r) { return *this ; } 
}; 

g ++ 7.0 đưa ra một thông báo lỗi tốt giải thích lý do tại sao việc khấu trừ thất bại:

deduced conflicting types for parameter '_T' ('int' and 'A<int>') 

Một giải pháp khả thi/workaround đang thêm tham số mẫu phụ:

template <typename _T, typename _U> 
A<_T> & operator += (A<_T> & l, _U r) { return l ; } 

wandbox example

0

Đó là một chút khó khăn. Trong trường hợp thứ hai của bạn, toán tử của bạn là một hàm thành viên của mẫu lớp A, không phải là bản thân mẫu.Khi bạn gọi số e += f, kết quả trùng khớp được tìm thấy là A<int>::operator += (int), đã tồn tại bên trong A<int>. Có chuyển đổi tiềm ẩn từ A<int> đến int, vì vậy tình trạng quá tải này hợp lệ.

Trong trường hợp đầu tiên của bạn, toán tử là một mẫu: trình biên dịch cố gắng instanciate nó bằng cách suy luận các đối số _T từ trang web cuộc gọi một mình. Khấu trừ đối số mẫu không xem xét chuyển đổi do người dùng xác định, do đó khấu trừ không thành công.

Giải pháp là để ngăn chặn tham số thứ hai tham gia vào khấu trừ, bằng cách sử dụng một bối cảnh phi deducible như một gián tiếp bổ sung thông qua một khuôn mẫu:

template <class T> 
struct NonDeduced_ { using type = T; } 

template <class T> 
using NonDeduced = typename NonDeduced_<T>::type; 

template <typename _T > 
A<_T> & operator += (A<_T> & l, NonDeduced<_T> r) { return l ; } 

Sau đó, chỉ có tham số đầu tiên tham gia vào khấu trừ , thành công, thì khấu trừ _T được sử dụng để xem liệu tham số thứ hai có chuyển đổi khả thi hay không.

3

Câu trả lời ngắn gọn ghê gớm là bạn đã sử dụng _T trong mã người dùng, vì vậy toàn bộ chương trình của bạn bị ốm và không cần chẩn đoán; số nhận dạng bao gồm _ theo sau là một chữ cái viết hoa được dành riêng để sử dụng bởi việc triển khai.

Trong ý nghĩa đó, cả hai ví dụ đều giống hệt nhau.

Bỏ qua lỗi đó, chúng không giống nhau.

Đầu tiên là người không phải là thành viên template+=.

Thứ hai là thành viên không phải là mẫu += của lớp mẫu có tham sốtiềm ẩn làm thông số đầu tiên.

Đây là những điều rất khác nhau. template đối sánh mẫu chức năng không thực hiện chuyển đổi (không phải từ cơ sở); phương pháp của template loại có thể chuyển đổi.

Trong trường hợp thứ hai, mẫu không operator+= có thể chuyển đổi đối số thứ hai thành loại _T. Trong trường hợp đầu tiên, mẫu operator+= sẽ không cố gắng chuyển đổi trong khi các loại đối sánh mẫu.

Có thực sự là một khả năng thứ 3, mà tôi thường thích:

template < class T > 
struct A { 
    operator T() const { return 42 ; } 
    friend A& operator += (A& l, T r) { return l; (void)r; } 
}; 

nơi chúng tôi làm cho một miễn phí += làm bạn. Điều này tạo ra một mẫu không += có hai đối số và do đó đối xứng hơn.

Bạn bè không phải mẫu như vậy có thể được tìm thấy qua ADL.

live example.

Là một sang một bên, chúng cũng khác nhau vì một con trỏ đến một có thể được lưu trữ trong A<_T>& (A<_T>::*)(_T), số khác như A<_T>& (*)(A<_T>&, _T). Đây không phải là giống hệt nhau, cũng không phải chúng có thể được chuyển đổi giữa chúng.

+0

Bạn bị đảo ngược đầu tiên và thứ hai. – Kundor

+0

@kund đổi thành tsrif và dnoces. Cám ơn! – Yakk

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