2012-05-18 47 views
40

Tôi có lớp sử dụng lớp lồng nhau và muốn sử dụng lớp lồng nhau operator<< để xác định operator<< ở lớp trên. Sau đây là cách mã của tôi trông giống như:Toán tử tải trọng <<: không thể ràng buộc lvalue thành ‘std :: basic_ostream <char> &&’

#include <memory> 
#include <iostream> 

template<typename T> 
struct classA { 
    struct classB 
    { 
    template<typename U> 
    friend inline std::ostream& operator<< (std::ostream &out, 
              const typename classA<U>::classB &b); 
    }; 

    classB root; 

    template<typename U> 
    friend std::ostream& operator<< (std::ostream &out, 
            const classA<U> &tree); 
}; 

template<typename T> 
inline std::ostream& operator<< (std::ostream &out, 
           const classA<T> &tree) 
{ 
    out << tree.root; 
    return out; 
} 

template<typename T> 
inline std::ostream& operator<< (std::ostream &out, 
           const typename classA<T>::classB &b) 
{ 
    return out; 
} 

int main() 
{ 
    classA<int> a; 
    std::cout << a; 
} 
  • Khi biên dịch mà không cần sự hỗ trợ cho C++ 11, định nghĩa về hành < < cho lớp bên trong có vẻ như không được tìm thấy bởi trình biên dịch:

    so.hpp:24:7: error: no match for ‘operator<<’ in ‘out << tree.classA<int>::root’ 
    so.hpp:24:7: note: candidates are: ... 
    
  • với GCC 4.6 và 4.7 khi biên dịch với std = C++ 0x:

    so.hpp:21:3: error: cannot bind ‘std::ostream {aka std::basic_ostream<char>}’ lvalue to ‘std::basic_ostream<char>&&’ 
    In file included from /usr/include/c++/4.7/iostream:40:0, 
           from so.hpp:2: 
    /usr/include/c++/4.7/ostream:600:5: error: initializing argument 1 of ‘std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = classA<int>::classB]’ 
    

Ai đó có thể cho tôi biết tại sao mã này không hợp pháp và cách tốt nhất để làm những gì tôi muốn là gì?

Trả lời

23

Bo cung cấp lý do tại sao điều này đang xảy ra (loại T không deducible trong cuộc gọi đến lồng nhau operator<<. Một cách giải quyết đơn giản cho điều này, và một cái gì đó mà tôi khuyên bạn nên nói chung, không chỉ ở đây, không được kết bạn với . một mẫu, mà là một chức năng miễn phí đơn cho rằng bạn sẽ cần phải xác định chức năng inline:.

template<typename T> 
struct classA { 
    struct classB 
    { 
    friend inline std::ostream& operator<< (std::ostream &out, 
              const classB &b) { 
     // definition goes here 
    } 
    }; 

    classB root; 

    friend std::ostream& operator<< (std::ostream &out, 
            const classA<U> &tree) { 
     // definition goes here 
    } 
}; 

có một vài sự khác biệt giữa hai phương pháp Điều quan trọng nhất là phương pháp này sẽ có trình biên dịch xác định tình trạng quá tải không có templated cho operator<< cho mỗi phiên bản của mẫu, vì nó không còn là mẫu, không phụ thuộc vào việc suy luận các đối số.Một tác dụng phụ khác là cách tiếp cận là một chút chặt chẽ hơn (bạn chỉ đang kết bạn với một chức năng, trong khi tiếp cận ban đầu của bạn là bạn đã làm mẫu và tất cả các cảnh báo có thể có (có thể được sử dụng như lỗ hổng để truy cập vào lớp học của bạn) internals). Cuối cùng các chức năng để xác định sẽ chỉ được tìm thấy thông qua ADL, do đó ít quá tải của operator<< cho trình biên dịch để xem xét khi lập luận không phải là ClassA<T> hay ClassA<T>::ClassB.


Làm thế nào truy cập có thể đạt được với cách tiếp cận của bạn

namespace { 
    struct intruder { 
     ClassA & ref; 
     intruder(ClassA& r) : ref(r) {} 
    }; 
    template <> 
    std::ostream& operator<< <intruder>(std::ostream& _, ClassA<intruder> const& i) { 
     std::cout << i.ref.private_member << std::endl; 
     return _; 
    } 
} 

Alternative

Hoặc bạn có thể kết bạn với một chuyên môn cụ thể của một mẫu. Điều đó sẽ giải quyết vấn đề intruder, vì nó sẽ chỉ được mở cho operator<< đến ClassA<intruder>, có tác động ít hơn nhiều. Nhưng điều này sẽ không giải quyết vấn đề cụ thể của bạn, vì loại vẫn không thể khấu trừ được.

+0

Cảm ơn bạn đã cung cấp giải pháp thay thế. – Antoine

+0

Tôi thấy nó giống như thiết kế * tốt hơn * thay vì * giải pháp thay thế *. Nó có nhược điểm của nó (bạn không thể lấy địa chỉ của hàm friend được khai báo bên trong lớp template), nhưng trên tất cả các tài khoản khác, nó phù hợp hơn để cung cấp các toán tử miễn phí ... –

+0

Đây là [Tạo mới bạn bè] (https : //en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Making_New_Friends) thành ngữ, do đó, không thực sự là một cách giải quyết. – TBBle

28

Bạn có một vấn đề với một "non-deducible context" trong toán tử này

template<typename T> 
inline std::ostream& operator<< (std::ostream &out, 
           const typename classA<T>::classB &b) 
{ 
    return out; 
} 

Trình biên dịch không thể tìm ra những gì giá trị của T sẽ dẫn đến một classB phù hợp với thông số bạn muốn vượt qua. Vì vậy, mẫu này không được xem xét!

Trong chế độ 11 C++, trình biên dịch sau đó đi vào để tìm một trận đấu chặt chẽ từ các thư viện chuẩn

operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) 

nơi nó thể trận đấu _Tp đến chỉ là về bất kỳ loại, bao gồm classA<T>::classB, nhưng ghi chú thông số đầu tiên không khớp.

+1

Nhận xét về kết quả trùng khớp đã giải thích rõ lý do tôi gặp sự cố tương tự. –

2

Hãy thử điều này:

template<typename T> 
inline std::ostream& operator<< (std::ostream &out, 
          const classA<T> &tree) 
{ 
    //out << tree.root; 
    ::operator<<(out, tree.root); 
    return out; 
} 

và sau đó bạn sẽ nhận được một lời thú nhận thẳng thắn của sự thiếu khả năng:

test.cpp:34:3: error: no matching function for call to ‘operator<<(std::ostream&, const classA<int>::classB&)’ 
test.cpp:34:3: note: candidates are: 
test.cpp:23:22: note: template<class T> std::ostream& operator<<(std::ostream&, const  typename classA<T>::classB&) 
test.cpp:30:22: note: template<class T> std::ostream& operator<<(std::ostream&, const classA<T>&) 

Cách giải quyết: có thể bạn có thể sử dụng một hàm thành viên trong lồng ClassB, và sử dụng nó thay vì nhà điều hành < < ... Tất nhiên, giải pháp đó có vô số nhược điểm, nhưng nó có thể giúp bạn thoát khỏi sự vội vã này.

+1

Bạn luôn có thể gọi ':: operator << (out, tree.root)' thay vào đó, nhưng tôi nghĩ rằng chức năng thành viên được đặt tên bây giờ trông hấp dẫn hơn. – Useless

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