2013-02-14 24 views
5

Theo như tôi hiểu C++ 11 tài liệu tham khảo, tôi không nên ràng buộc một tham chiếu rvalue đến một tham chiếu lvalue (non-const) như trước đây có thể được ràng buộc với một tạm thời và sau này không bao giờ bị ràng buộc với một tạm thời.Tại sao tôi có thể std :: di chuyển ref rvalue stream vào ref ref lvalue?

Tuy nhiên tôi thấy hành vi này lẻ kết hợp đối tượng dòng tạm thời (mà tôi giảm như xa như tôi có thể)

struct Dummy {}; 
template <typename Stream> 
Stream& operator<<(Stream& s, Dummy) { 
    return s << ".";   // <- (A) 
} 

template <typename Stream> 
void pass(Stream&& s) { 
    std::move(s) << Dummy(); // <- (X) rvalue->lvalue conversion? 
} 

#include <fstream> 
int main() { 
    pass(std::fstream("test",std::ios::out)); 
} 

Nếu tôi viết s << Dummy() trong dòng (X), C++ phàn nàn phù (A), nói

error: invalid initialization of reference of type ‘std::basic_fstream<char>&’ from expression of type ‘std::basic_ostream<char>’

Tuy nhiên, tại sao mã (như được hiển thị ở trên) biên dịch và hoạt động như mong đợi? Tham chiếu rvalue được trả về bởi std::move chỉ nên không thể bị ràng buộc với tham chiếu giá trị bằng lóng như biểu thức s, nhưng cả hai số gcc 4.6.1gcc 4.7.2 đều phản ứng giống nhau.

Và tại sao hiện tượng này chỉ xuất hiện để hoạt động với luồng? Khi trực tiếp chuyển một Dummy&& đến một hàm mong đợi một T& không thành công cả khi có và không có std::move.

Trả lời

10

basic_ostream có tình trạng quá tải của operator<< trông như thế này:

template <typename Elem, typename Traits, typename T> 
basic_ostream<Elem, Traits>& 
    operator<<(basic_ostream<Elem, Traits>&& sink, const T& val) 
{ 
    return sink << val; 
} 

này được gọi là "rvalue dòng chèn" trong tiêu chuẩn, tại §27.7.3.9 [ostream.rvalue].

Nó cho phép chuyển đổi tiềm ẩn (của các loại) từ một giá trị basic_ostream thành giá trị. Nó được giới thiệu cụ thể để cho phép temporary streams to be usable without resorting to tricks.


Đối với lý do tại sao các biên dịch không thành công khi bỏ qua bạn di chuyển:

Khi Stream& operator<<(Stream& s, Dummy) được gọi mà không di chuyển, Stream sẽ std::fstream mà kế thừa từ std::ostream (ví dụ: basic_ostream<char>).

Nó sẽ sử dụng quá tải basic_ostream<E, T>& operator<<(basic_ostream<E, T>&, const char*) để chèn chuỗi của bạn, sau đó cố gắng trả lại kết quả của biểu thức đó sẽ là ostream. Bạn không thể hoàn toàn bị downcast từ std::ostream& đến std::fstream&, do đó bạn gặp lỗi.

Bạn có thể khắc phục điều này bằng cách trả lại s trên một dòng riêng của nó (nơi mà nó sẽ không có được upcasted ngầm.)

Đây không phải là một vấn đề với di chuyển vì bạn đi qua đó rvalue-to- toán tử chèn lvalue mà chúng ta vừa phát hiện trước. Bên trong hàm đó, luồng là basic_ostream và do đó, Stream cũng như các loại trả về sẽ khớp.

+1

Tôi hiểu. Điều đó có ý nghĩa. Nhưng tại sao 'std :: move' lại cần thiết, vậy thì sao? – bitmask

+0

@bitmask: Bởi vì không có nó 's' không thể liên kết với một tham chiếu rvalue, cho rằng nó là một lvalue. –

+0

@KerrekSB: Nhưng nó có một quá tải hoàn toàn tốt cho một dòng tham chiếu rvalue để ràng buộc với (như được hiển thị trong câu trả lời này). Tại sao không phải là quá tải phù hợp mà không khuyến khích từ 'std :: move'? – bitmask