2014-04-27 17 views
5

Theo câu hỏi How can I detect if a type can be streamed to an std::ostream? Tôi đã viết một lớp đặc điểm cho biết nếu một số loại có thể được phát trực tiếp tới luồng IO. Đặc điểm này dường như hoạt động tốt cho đến bây giờ mà tôi đã phát hiện ra một vấn đề.Tại sao toán tử tra cứu lớp mẫu tra cứu đặc điểm của tôi << đối với llvm :: StringRef?

Tôi đang sử dụng mã bên trong một dự án sử dụng LLVM và tôi đang sử dụng lớp StringRef của họ (tương tự như tinh thần với đề xuất std :: string_view). Here là liên kết tới tài liệu Doxygen cho lớp học, từ đó bạn có thể tìm thấy tệp tiêu đề khai báo nếu cần. Vì LLVM không cung cấp toán tử < < để truyền các đối tượng StringRef tới luồng std (chúng sử dụng lớp luồng tùy chỉnh nhẹ), tôi đã viết một.

Tuy nhiên, khi tôi sử dụng đặc điểm nó không hoạt động nếu điều hành tùy chỉnh của tôi < < được khai báo sau các đặc điểm (điều này xảy ra bởi vì tôi có các đặc điểm trong một header và các nhà điều hành < < chức năng trong một số khác) . Tôi đã từng nghĩ rằng việc tra cứu trong các mẫu instantiations làm việc từ quan điểm của điểm instantiation, vì vậy tôi nghĩ rằng nó sẽ làm việc. Trên thực tế, như bạn có thể thấy bên dưới, với một lớp khác và toán tử tùy chỉnh < <, được khai báo sau đặc điểm, mọi thứ hoạt động như mong đợi (đó là lý do tại sao tôi mới phát hiện vấn đề này), vì vậy tôi không thể tìm ra điều gì khiến StringRef đặc biệt.

Đây là ví dụ hoàn chỉnh:

#include <iostream> 

#include "llvm/ADT/StringRef.h" 

// Trait class exactly from the cited question's accepted answer 
template<typename T> 
class is_streamable 
{ 
    template<typename SS, typename TT> 
    static auto test(int) 
     -> decltype(std::declval<SS&>() << std::declval<TT>(), 
        std::true_type()); 

    template<typename, typename> 
    static auto test(...) -> std::false_type; 

public: 
    static const bool value = decltype(test<std::ostream,T>(0))::value; 
}; 

// Custom stream operator for StringRef, declared after the trait 
inline std::ostream &operator<<(std::ostream &s, llvm::StringRef const&str) { 
    return s << str.str(); 
} 

// Another example class 
class Foo { }; 
// Same stream operator declared after the trait 
inline std::ostream &operator<<(std::ostream &s, Foo const&) { 
    return s << "LoL\n"; 
} 

int main() 
{ 
    std::cout << std::boolalpha << is_streamable<llvm::StringRef>::value << "\n"; 
    std::cout << std::boolalpha << is_streamable<Foo>::value << "\n"; 

    return 0; 
} 

Trái với mong đợi của tôi, bản in này:

false 
true 

Nếu tôi chuyển việc kê khai của người điều khiển < < cho StringRef trước việc kê khai đặc điểm , nó in đúng. Vậy tại sao điều kỳ lạ này lại xảy ra và tôi có thể khắc phục vấn đề này như thế nào?

+0

Đặt toán tử của bạn trong cùng một không gian tên làm loại để bật ADL. – Yakk

+0

@Yakk Đó là câu trả lời, vậy tại sao không viết một câu trả lời? – jrok

+1

@jrok bởi vì tôi đang đặt em bé ngủ một giấc ngủ ngắn, và đó không phải là lạm dụng để kiểm tra nó là vấn đề thực sự và quá trình xây dựng, vv :) ​​ – Yakk

Trả lời

1

Như đã đề cập bởi Yakk, đây chỉ đơn giản là ADL: Tra cứu phụ thuộc đối số.

Nếu bạn không muốn bận tâm, hãy nhớ rằng bạn nên luôn viết một hàm miễn phí trong cùng một không gian tên như ít nhất một trong các đối số của nó. Trong trường hợp của bạn, vì nó bị cấm thêm các hàm vào std, nó có nghĩa là thêm hàm của bạn vào không gian tên llvm. Thực tế là bạn cần phải hội đủ điều kiện đối số StringRef với llvm:: là đã chết.

Các quy tắc của độ phân giải chức năng là khá phức tạp, nhưng như một phác thảo nhanh chóng:

  • tra cứu tên: thu thập một tập hợp các ứng cử viên tiềm năng có độ phân giải
  • quá tải: chọn ứng cử viên tốt nhất trong số các tiềm năng
  • độ phân giải chuyên môn: nếu ứng cử viên là mẫu chức năng, hãy kiểm tra bất kỳ chuyên môn nào có thể áp dụng

Giai đoạn tra cứu tên mà chúng tôi đang sử dụng rned với ở đây là tương đối đơn giản. Tóm lại:

  • nó quét không gian tên của đối số, sau đó là cha mẹ, ...cho đến khi nó đạt đến phạm vi toàn cầu
  • sau đó tiến hành bằng cách quét các phạm vi hiện tại, sau đó phạm vi mẹ, ... cho đến khi nó đạt đến phạm vi toàn cầu

lẽ to allow shadowing (như đối với bất kỳ tên nào khác tra cứu), tra cứu dừng lại ở phạm vi đầu tiên trong đó nó gặp một trận đấu và vô tình bỏ qua bất kỳ phạm vi xung quanh nào.

Lưu ý rằng using chỉ thị (ví dụ: using ::operator<<;) có thể được sử dụng để giới thiệu tên từ phạm vi khác. Mặc dù vậy, điều đó rất nặng nề, vì nó đặt hành vi trên máy khách, vì vậy xin vui lòng không dựa vào sự sẵn có của nó như là một cái cớ cho sự cẩu thả (mà tôi đã thấy: x).


Ví dụ về shadowing: đây in "Hello, World" mà không tăng một lỗi mơ hồ.

#include <iostream> 

namespace hello { namespace world { struct A{}; } } 

namespace hello { void print(world::A) { std::cout << "Hello\n"; } } 

namespace hello { namespace world { void print(A) { std::cout << "Hello, World\n"; } } } 

int main() { 
    hello::world::A a; 
    print(a); 
    return 0; 
} 

Ví dụ về interrupted search: ::hello::world mang lại một hàm có tên print vì thế nó được chọn ra mặc dù nó không phù hợp chút nào và ::hello::print sẽ là một trận đấu chặt chẽ hơn.

#include <iostream> 

namespace hello { namespace world { struct A {}; } } 

namespace hello { void print(world::A) { } } 

namespace hello { namespace world { void print() {} } }; 

int main() { 
    hello::world::A a; 
    print(a); // error: too many arguments to function ‘void hello::world::print()’ 
    return 0; 
}