2014-04-27 75 views
5

在問題How can I detect if a type can be streamed to an std::ostream?之後,我寫了一個特徵類,說明某種類型是否可以流式傳輸到IO流。直到現在我發現了一個問題,這個特質似乎運作良好。爲什麼我的特徵模板類查找運算符<< for llvm :: StringRef?

我使用的是使用LLVM的項目中的代碼,我使用它們的StringRef類(它與提議的std :: string_view類似)。 Here是Doxygen文檔的類的鏈接,如果需要,可以從中找到它的聲明頭文件。由於LLVM不提供運算符將StringRef對象傳送到標準流(它們使用自定義的輕量級流類),因此我寫了一個。

然而,當我使用的特點這是行不通的,如果我的運營商定製< <聲明後性狀(出現這種情況,因爲我有一個頭的特點和操作< <功能的另一個) 。我曾經認爲模板實例化中的查找是從實例化點的角度來看的,所以我認爲它應該起作用。其實,正如你可以在下面看到的那樣,另一個類和它的自定義運算符< <,在特徵後面聲明,一切都按預期工作(這就是爲什麼我現在才發現這個問題),所以我無法弄清楚是什麼讓StringRef特別。

這是完整的例子:

#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; 
} 

出乎我的意料,這個打印:

false 
true 

如果我性狀聲明之前移動運營商< <爲StringRef 的聲明它打印真實。 那麼爲什麼會發生這種奇怪的事情,我該如何解決這個問題?

+0

將您的操作符置於與啓用ADL的類型相同的命名空間中。 – Yakk

+0

@Yakk這就是答案,那麼爲什麼不寫一個呢? – jrok

+1

@jrok因爲我正在給寶寶睡覺睡覺,這並不妨礙檢查它是真正的問題和詳細說明等。:) – Yakk

回答

1

正如Yakk所提到的,這僅僅是ADL:參數依賴查找。

如果你不想打擾,只要記住你應該總是在同一個命名空間中至少寫一個自由函數的參數。就你而言,由於禁止將函數添加到std,這意味着將你的函數添加到llvm名稱空間中。您需要通過llvm::來驗證StringRef參數的事實是一個不爭的事實。

函數解析的規則是相當複雜的,但作爲一個速寫:

  • 名稱查找:收集一組潛在候選
  • 重載解析:挑選潛力之間的最佳人選
  • 專業化解決方案:如果候選人是功能模板,請檢查是否可以應用任何專業化

我們認爲的名稱查找階段與這裏一起比較簡單。總之:

  • 它掃描參數的名稱空間,然後他們的父母,...直到它到達全球範圍內
  • 然後通過掃描當前範圍內進行,那麼它的父範圍,......直到它到達全球範圍內

大概to allow shadowing(像其他任何名稱查找),查找在遇到匹配的第一個範圍停止,並傲慢地忽略任何周圍的範圍。

請注意,using指令(例如using ::operator<<;)可用於從另一個範圍引入名稱。儘管這是一個沉重的負擔,因爲它把責任推給了客戶,所以請不要依賴它的可用性來作爲溜溜的藉口(我已經看到了這一點:x)。


shadowing實施例:此打印"Hello, World"而不引發歧義錯誤。

#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; 
} 

interrupted search例子:::hello::world產生了一個名爲功能使print它挑出來,即使它不匹配都和::hello::print會是一個嚴格的更好的匹配。

#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; 
}