2016-08-01 78 views
4

對於和std::vectoroperator<<過載,我無法使用標準中指定的(正確)clang實施的兩相查找。使用通用模板函數專門化運算符<<用於std :: ostream和std :: vector的最佳方法?

考慮其轉移參數轉化爲流一個非常通用的模板函數(實際上只用遞歸是有用的,但簡單的例子就足以引發問題):

// generic.h 
template<typename Stream, typename Arg> 
void shift(Stream& s, Arg& arg) { s << arg; } 

這generic.h可使用整個項目。然後,在一些其他文件中,我們要輸出一個std::vector,所以我們定義一個過載

// vector.h 
#include <iostream> 
#include <vector> 
std::ostream& operator<<(std::ostream& s, std::vector<int> const& v) { 
    for(auto const& elem : v) { s << elem << ", "; } 
    return s; 
} 

和主文件中,我們首先(間接地)使用generic.h,然後,由於一些其它包括,載體過載:

// main.cpp 
#include "generic.h" 
#include "vector.h" 

int main() { 
    std::vector<int> v{1,2,3,4,5}; 
    shift(std::cout, v); 
} 

此代碼由GCC(5.4.0)和ICC(16.0)接受,但鐺抱怨call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup

惱人的是,鏗鏘是對的,我想解決這個問題在我的代碼。還有據我可以看到三個選項:

  • 移動的operator<<定義shift()之前。這具有的缺點是,當包括間接包括generic.hvector.h的一些(可能是其他)文件時,還必須小心地對它們進行正確排序。

  • 使用自定義名稱空間,將所需的所有內容從std導入該名稱空間,並在名稱空間內的新名稱空間類上定義運算符,以便ADL可以找到它。

  • std命名空間中定義operator<<。我認爲這是未定義的行爲。

我錯過任何選項嗎?一般來說,定義std函數的重載最好的方法是什麼 - 只有類(如果我想移動NS::MyClass,則不存在問題,因爲我可以在NS中定義運算符)。

回答

11

不要超載你不控制類型,如運營商:

std::ostream& operator<<(std::ostream& s, std::vector<int> const& v); 

而是創建一個小的適配器類,並定義了運營商,例如:

template<typename T> struct PrintableVector { 
    std::vector<T> const* vec; 
} 

template<typename T> 
std::ostream& operator<<(std::ostream& s, PrintableVector<T> v) { 
    for(auto const& elem : *v.vec) { s << elem << ", "; } 
    return s; 
} 

可以這樣使用:

shift(std::cout, PrintableVector<int>{&v}); 

你可以把適配器放在你喜歡的任何命名空間中,在相同的命名空間中重載運算符,以便ADL可以找到它。

這避免了查找問題,不需要添加任何東西的命名空間std,並且不會嘗試唯一地確定這是什麼意思打印vector<int>(如果其他一些代碼假定這可能會導致在程序的其他部分問題向量不可打印,或試圖爲它們定義自己的重載)。

+3

如果你要使用指針,你可能應該檢查'nullptr'。否則,只需使用參考或可選類型 – KABoissonneault

+2

是的。很顯然,可以進行調整,具體取決於你想要如何使用它或者想要如何防守。引用的缺點是類型不可分配。如果這沒關係,那麼請使用參考。答案的要點是「使用適配器而不是爲不屬於你的類型重載操作符」,而不是「如何編寫適合所有人的通用適配器」。 –

+0

重新引用不可分配引用的缺點:使用如此輕量級的包裝器,您可以隨時重新創建一個,因此我同意@KABoissonneault使用引用而不是指針。 – TemplateRex

0

我跟着Jonathan’s advice並使用包裝Printable<>來定義operator<<。通過使這個包裝也隱式轉換爲原始類型,我可以處理這兩種情況,其中只有Printable<T>可打印,以及那些也是T本身是可打印的。代碼如下:

template<typename T> 
struct Printable { 
    T const& ref; 
    Printable(T const& ref) : ref(ref) { } 
    operator T const&() { return ref; } 
}; 

template<typename T> 
Printable<T> printable(T const& in) { return Printable<T>(in); } 

template<typename Stream, typename Arg> 
void shift(Stream& s, Arg& arg) { 
    s << printable(arg); 
} 

#include <iostream> 
#include <vector> 
std::ostream& operator<<(std::ostream& out, Printable<std::vector<int> > const& v) { 
    for(auto const& elem : v.ref) { s << elem << ", "; } 
    return s; 
} 

struct MyClass { }; 
std::ostream& operator<<(std::ostream& s, MyClass const& m) { 
    return s << "MyClass\n"; 
} 

int main() { 
    std::vector<int> v{1,2,3}; 
    MyClass m; 

    shift(std::cout, v); 
    shift(std::cout, m); 
} 

這有在呼叫中使用的點shift(),我不關心什麼樣的類型我的變量有優勢。只有在operator<<類的定義中,我必須小心以及使用這樣的操作符時。