2012-01-17 34 views
17

Tôi muốn biết tại sao chuyển đổi loại ngầm định không hoạt động với quá tải toán tử bên ngoài trên các mẫu lớp. Dưới đây là làm việc, không templated phiên bản:Chuyển đổi ngầm định khi các toán tử quá tải cho các lớp mẫu

class foo 
{ 
public: 

    foo() = default; 

    foo(int that) 
    {} 

    foo& operator +=(foo rhs) 
    { 
     return *this; 
    } 
}; 

foo operator +(foo lhs, foo rhs) 
{ 
    lhs += rhs; 
    return lhs; 
} 

Đúng như dự đoán, những dòng sau biên dịch một cách chính xác:

foo f, g; 
f = f + g; // OK 
f += 5; // OK 
f = f + 5; // OK 
f = 5 + f; // OK 

Mặt khác, khi lớp foo được khai báo là một mẫu đơn giản như thế này:

template< typename T > 
class foo 
{ 
public: 

    foo() = default; 

    foo(int that) 
    {} 

    foo& operator +=(foo rhs) 
    { 
     return *this; 
    } 
}; 

template< typename T > 
foo<T> operator +(foo<T> lhs, foo<T> rhs) 
{ 
    lhs += rhs; 
    return lhs; 
} 

Các dòng sau biên dịch với các lỗi:

foo<int> f, g; 
f = f + g; // OK 
f += 5; // OK 
f = f + 5; // Error (no match for operator+) 
f = 5 + f; // Error (no match for operator+) 

Tôi muốn hiểu tại sao trình biên dịch (GCC 4.6.2) không thể thực hiện chuyển đổi kiểu ngầm bằng cách sử dụng hàm tạo chuyển đổi cho phiên bản mẫu của lớp. Đó có phải là hành vi mong đợi không? Ngoài việc tạo thủ công tất cả các tình trạng quá tải cần thiết, có cách nào khác cho việc này không?

+0

Bạn có thể thêm một tình trạng quá tải cho T: foo < T > operator + (foo < T > LHS, T RHS) –

Trả lời

11

Lý do nó không chỉ hoạt động là chuyển đổi loại tiềm ẩn (nghĩa là thông qua nhà thầu) không áp dụng trong quá trình khấu trừ đối số mẫu. Nhưng nó hoạt động nếu bạn làm cho người điều hành bên ngoài một người bạn kể từ đó loại T được biết, cho phép trình biên dịch điều tra những gì có thể được đúc để làm cho các đối số khớp nhau.

Tôi đã tạo một ví dụ dựa trên của bạn (nhưng đã loại bỏ các công cụ C++ 11), lấy cảm hứng từ Khoản 46 (một số lượng hợp lý) trong Scott Meyers Hiệu quả C++ (ed 3). Câu hỏi của bạn gần như khớp chính xác với mục đó. Scott cũng lưu ý rằng ... "việc sử dụng bạn này không liên quan đến việc truy cập các phần không công khai của lớp."

này cũng sẽ cho phép làm việc với hỗn hợp của foo < T>, foo < U> vv miễn là T và U có thể được bổ sung, vv

Ngoài ra nhìn vào bài đăng này: C++ addition overload ambiguity

#include <iostream> 

using namespace std; 

template< class T > 
class foo 
{ 
private: 
    T _value; 
public: 
    foo() : _value() {} 

    template <class U> 
    foo(const foo<U>& that) : _value(that.getval()) {} 

    // I'm sure this it can be done without this being public also; 
    T getval() const { return _value ; }; 

    foo(const T& that) : _value(that) {} 

    friend const foo operator +(foo &lhs,const foo &rhs) 
     { 
    foo result(lhs._value+rhs._value); 
    return result; 
     }; 
    friend const foo operator +(foo &lhs,const T &rhsval) 
     { 
    foo result(lhs._value+rhsval); 
    return result; 
     }; 
    friend const foo operator +(const T &lhsval,foo &rhs) 
     { 
    foo result(lhsval+rhs._value); 
    return result; 
     }; 

    friend foo& operator +=(foo &lhs,const foo &rhs) 
     { 
    lhs._value+=rhs._value; 
    return lhs; 
     }; 
    friend std::ostream& operator<<(std::ostream& out, const foo& me){ 
     return out <<me._value; 
    } 
}; 

int main(){ 
    foo<int> f, g; 
    foo<double> dd; 
    cout <<f<<endl; 
    f = f + g; 
    cout <<f<<endl; 
    f += 3 ; 
    cout <<f<<endl; 
    f = f + 5; 
    cout <<f<<endl; 
    f = 7 + f; 
    cout <<f<<endl;  
    dd=dd+f; 
    cout <<dd<<endl;  
    dd=f+dd; 
    cout <<dd<<endl;  
    dd=dd+7.3; 
    cout <<dd<<endl;    
} 
+0

Đây chính xác là những gì tôi cần! Cảm ơn nhiều! Tôi chắc chắn cần phải lấy bản thân mình một bản sao của hiệu quả C++ ... – pmjobin

+0

tuyệt vời, tôi đã cố gắng để viết một câu trả lời cụ thể cho câu hỏi càng tốt. –

+0

Cú pháp để viết các hàm của bạn bè là gì? Nếu tôi xóa định nghĩa, tôi gặp lỗi "khai báo chức năng không phải mẫu". –

2

Tất cả có thể foo<T> là các chuyển đổi hợp lệ như nhau từ int do hàm tạo mất int, không phải loại mẫu. Trình biên dịch không thể sử dụng tham số khác trong toán tử để đoán tham số nào bạn có thể muốn, vì vậy bạn gặp lỗi. Nếu bạn nói một cách rõ ràng, bạn sẽ tin rằng nó sẽ hoạt động như thế nào.

+0

Nhưng những lập luận là 'foo 'và' foo ', không' foo ' và 'foo '. Vì vậy, bất kỳ 'T' nào khác hơn' int' sẽ không khớp với đối số đầu tiên và phải bị loại bỏ. Đúng? –

+0

Thậm chí nếu bạn thay đổi kiểu 'đó' trong hàm tạo thành' T' thì điều này vẫn không hoạt động (ít nhất là với MSVC10). Tôi đã thử một thời gian ngắn để tìm ra một số đường đạn bạc trong tiêu chuẩn để giải thích điều này, nhưng tốt nhất tôi có thể đoán là có một kịch bản gà và trứng xảy ra liên quan đến độ phân giải quá tải với chuyển đổi ngầm định, và khấu trừ đối số mẫu. Tôi nghĩ rằng trình biên dịch không thể hoàn thành một trình duyệt mà không có trình duyệt khác và ngược lại trong trường hợp này, do đó yêu cầu quá tải rõ ràng hơn. – brendanw

8

Tôi đặt câu hỏi này cho các tác giả thư viện tại MS và nhận được một phản hồi cực kỳ thông tin từ Stephan Lavavej, vì vậy tôi cho anh ta đầy đủ tín dụng cho thông tin này.

Lỗi biên dịch bạn nhận được trong trường hợp mẫu là do việc loại trừ đối số mẫu chạy trước độ phân giải quá tải và đối số mẫu cần khớp chính xác để thêm bất kỳ thứ gì vào tập quá tải.

Cụ thể, khấu trừ đối số mẫu xem xét từng cặp tham số loại P và loại đối số A và cố gắng tìm các thay thế mẫu sẽ thực hiện A chính xác khớp P. Sau khi tìm kết quả phù hợp cho mỗi đối số, nó kiểm tra tính nhất quán (để nếu bạn gọi bar(foo<T>, foo<T>) với T = int cho tham số đầu tiên và T = double làm tham số thứ hai thì nó cũng không thành công).Chỉ sau khi chính xác, các kết quả phù hợp được thay thế thành công trong chữ ký hàm là chữ ký được thêm vào tập hợp các hàm ứng cử viên để giải quyết quá tải.

Chỉ sau khi tất cả các chức năng thông thường (tìm thấy thông qua tra cứu tên) và chữ ký mẫu chức năng phù hợp đã được thêm vào tập quá tải là chạy quá tải, tại thời điểm đó tất cả các chữ ký chức năng này được đánh giá là "phù hợp nhất" thời gian chuyển đổi tiềm ẩn nào sẽ được xem xét.

Đối với trường hợp operator+(foo<T>, foo<T>) với foo<int> + 5, khấu trừ mẫu đối số có thể tìm thấy không có thay thế cho T mà sẽ làm cho sự biểu hiện foo<T>chính xác trận đấu int, do đó tình trạng quá tải của nhà điều hành + bị ném ra như một ứng cử viên và chuyển đổi ngầm không bao giờ thậm chí đã xem.

Ý kiến ​​ở đây dường như là điều này thường là một điều tốt, vì nó làm cho các mẫu có thể dự đoán được nhiều hơn, để lại phạm vi hành vi tiềm ẩn lạ đối với độ phân giải quá tải.

Các tiêu chuẩn có nhiều chuyện để nói về vấn đề này tại địa chỉ:

14.8.2.1 Suy luận đối số mẫu từ một cuộc gọi chức năng

"trích lập luận mẫu được thực hiện bằng cách so sánh từng chức năng kiểu mẫu tham số (gọi nó là P) với loại đối số tương ứng của cuộc gọi (gọi là A) như được mô tả bên dưới. ...

... Nói chung, quá trình khấu trừ sẽ tìm các giá trị đối số mẫu sẽ làm cho suy luận A giống hệt với A (sau khi loại A được chuyển đổi như mô tả ở trên) "

Nó tiếp tục liệt kê một vài trường hợp đặc biệt mà quy tắc này có ngoại lệ liên quan đến vòng loại cv (vì vậy T & sẽ tương thích với const T &) , và kết hợp các lớp dẫn xuất (nó có thể trong một số trường hợp phù hợp với Derived & đến Base &) nhưng về cơ bản, khớp chính xác là quy tắc.

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