2011-05-13 103 views
3

我有一個可變參數函數,我想在第一個參數類型上重載。在可變參數模板函數中的ostream上重載

void write(void) { } 

void write(std::ostream&) { } 

template< typename Head, typename... Rest > 
void write(std::ostream& out, Head&& head, Rest&&... rest) 
{ 
    out << head; 
    write(out, std::forward<Rest>(rest)...); 
} 

template< typename... Args > 
void write(Args&&... args) 
{ 
    write(std::cout, std::forward<Args>(args)...); 
} 

但是這些函數並不像預期的那樣工作。

write("## to cout ##"); // printed to stdout as expected 
write(std::cerr, "## to cerr ##"); // printed to stderr as expected 
std::ostringstream oss; 
write(oss, "## to string ##"); // error here 
// '0x7fff9db8## to string ##' is printed to stdout! 

這是怎麼回事?
爲什麼重載分辨率選擇我想要的功能?
有沒有辦法做到這一點,沒有大量的元編程? (我可以用std::is_convertible解決這個問題,但是解決方案比我上面顯示的簡單代碼大得多)。

+0

爲什麼你需要最後一個模板專業化?如果沒有那個問題,問題就會消失。 – 2011-05-13 20:36:22

+0

@Diego,如果ostream對象不是由調用者提供的,最後一個專門化('write(args)')將'std :: cout'放置爲第一個參數。 – 2011-05-13 20:41:06

回答

5

這是因爲ostringstream需要一個基地轉化爲ostream,當你把它傳遞給其他的模板,而當你把它傳遞給轉發到write(std::cout, ...)模板不需要任何轉換。因此,如果您通過ostringstream,它會選擇更通用的模板,該模板將ostringstream作爲參數轉發,以輸出到更具體的模板。輸出ostringstream將其轉換爲void*,然後打印。

你可以用is_base_of來解決這個問題(對我來說感覺比使用is_convertible更好)。

template<typename Arg, typename... Args, typename = 
    typename std::enable_if< 
    !std::is_base_of< 
     std::ostream, 
     typename std::remove_reference<Arg>::type, 
     >::value>::type 
> 
void write(Arg&& arg, Args&&... args) 
{ 
    write(std::cout, std::forward<Arg>(arg), std::forward<Args>(args)...); 
} 

我個人不喜歡在我的代碼使用太多SFINAE,因爲我不能用尖括號一定程度的應對。所以我喜歡用超載

template< typename Arg, typename... Args > 
void write_dispatch(std::true_type, Arg&& arg, Args&&... args) 
{ 
    std::ostream& os = arg; 
    write(os, std::forward<Args>(args)...); 
} 

template< typename Arg, typename... Args > 
void write_dispatch(std::false_type, Arg&& arg, Args&&... args) 
{ 
    write(std::cout, std::forward<Arg>(arg), std::forward<Args>(args)...); 
} 

template< typename Arg, typename... Args > 
void write(Arg&& arg, Args&&... args) 
{ 
    typedef typename std::remove_reference<Arg>::type nonref_type; 
    write_dispatch(std::is_base_of<std::ostream, nonref_type>(), 
      std::forward<Arg>(arg), std::forward<Args>(args)...); 
} 

這樣,如果你不是左值的ostream其他的東西把它作爲第一個參數,它會調用write_dispatch,這將改變該呼叫轉變的ostream這樣一個左值,使您的其他write模板可以繼續。

最後一點,你應該說out << std::forward<Head>(head),否則你以前的遞歸步驟中使用std::forward的所有工作都是徒勞的,因爲最後你會輸出所有東西作爲左值。

+0

所以你說重載決議更喜歡T &&基礎類轉換?有沒有辦法可以調整簽名以避免這種情況? – 2011-05-13 20:38:56

+0

@deft_code,正如我假設你發現的那樣,你可以用'is_convertible '或'is_base_of '來做到這一點。 – 2011-05-13 20:39:55

+0

@litb,當T私下繼承'std :: ostream'時,'is_base_of'也不會成立?我不知道爲什麼有人會這樣做,但會導致示例中的static_cast不能編譯。 – 2011-05-13 22:54:21