2013-02-23 63 views
4

我不確定此代碼是否不能編譯。流操縱器的模板類型扣除

示例代碼我的工作:

#include <iostream> 
using std::cout; 
using std::endl; 

class Foo { 
    public: 
     template<typename T> 
     Foo& operator<<(const T& t) { 
      cout << t; 
      return *this; 
     } 
}; 

int main() { 
    Foo foo; 
    foo << "Hello World"; // perfectly fine 
    foo << endl; // shit hits the fan 

    return 0; 
} 

這是錯誤:

test.cpp:19:12: error: no match for ‘operator<<’ in ‘foo << std::endl’ 
test.cpp:19:12: note: candidates are: 
test.cpp:10:14: note: template<class T> Foo& Foo::operator<<(const T&) 
test.cpp:10:14: note: template argument deduction/substitution failed: 
test.cpp:19:12: note: couldn't deduce template parameter ‘T’ 

我很困惑,爲什麼它不能取代功能型endlostream& (*)(ostream&))對於T,在指定時顯然沒問題,當您指定cout << endl;

我發現它addi倚重令人費解的是這樣可以解決問題[編輯]

Foo& operator<<(ostream& (*f)(ostream&)) { 
    cout << f; 
    return *this; 
} 

如果這個問題不明確,我問爲什麼它不能推斷在首位的模板。

+0

你想達到什麼目的?你不是從'std :: basic_ostream '或者實現streambuf派生出來的任何特定原因? – sehe 2013-02-23 16:41:35

+0

最後一個問題:它首先無法推導出模板,因爲**多個重載**是適用的 - 因此函數引用的情況是不明確的。 – sehe 2013-02-23 17:03:04

回答

3

endl是一個操縱器,即它是一個未解決的功能類型。有幾個重載,類型扣除無法決定你想要哪一個。

更多specificly,這裏是endl樣子(在GNU libc中++):

/** 
* @brief Write a newline and flush the stream. 
* 
* This manipulator is often mistakenly used when a simple newline is 
* desired, leading to poor buffering performance. See 
* http://gcc.gnu.org/onlinedocs/libstdc++/manual/bk01pt11ch25s02.html 
* for more on this subject. 
*/ 
template<typename _CharT, typename _Traits> 
    inline basic_ostream<_CharT, _Traits>& 
    endl(basic_ostream<_CharT, _Traits>& __os) 
    { return flush(__os.put(__os.widen('\n'))); } 

更新所以,問題是,編譯器不能推斷實例其中endl你會路過(這是一個無法解決的超載)。 您可以改爲使用static_cast<ostream&(*)(ostream&)>(endl)來解決此問題。

當然,這不方便。這裏有一個簡單的解決辦法:http://liveworkspace.org/code/2F2VHe$1

#include <iostream> 
using std::cout; 
using std::endl; 

class Foo : public std::ostream 
{ 
    public: 
     template<typename T> 
     Foo& operator<<(T&& t) { 
      cout << std::forward<T>(t); 
      return *this; 
     } 

     typedef std::ostream& (manip)(std::ostream&); 

     Foo& operator<<(manip& m) { 
      cout << m; 
      return *this; 
     } 
}; 

int main() { 
    Foo foo; 
    foo << "Hello World"; // perfectly fine 
    foo << endl; // everything is fine 

    return 0; 
} 
+0

你能否進一步解釋,是因爲它實際上並沒有嘗試用不同的過載替代來確定正確的替代嗎?或者我對編譯器期望過高 – 2013-02-23 16:44:49

+0

@AnthonySottile我提供了一個簡單的示例來說明如何迎合操作符'endl':http://liveworkspace.org/code/2F2VHe$1 – sehe 2013-02-23 16:45:41

+0

您是否可以修改您的代碼和我上面的代碼和差異的原因。例如,我目前的「解決方案」在明確定義操縱器的重載時做了同樣的事情 - 但是你能解釋一下「std :: forward」的用法。你也可以解釋爲什麼編譯器無法在沒有明確定義的情況下選擇重載的原因。 – 2013-02-23 16:48:43

1

的問題是,endl被定義爲函數模板操縱。段的27.7.1的C++ 11標準規定它的簽名:對重載解析

template <class charT, class traits> 
basic_ostream<charT,traits>& endl(basic_ostream<charT,traits>& os); 
template <class charT, class traits> 

此外,每段13.3.1:

In each case where a candidate is a function template, candidate function template specializations are generated using template argument deduction (14.8.3, 14.8.2). Those candidates are then handled as candidate functions in the usual way.

operator <<被定義爲模板,和編譯器需要推斷出T的類型。但是,編譯器如何知道您的實例是endl?它如何推導出模板參數charTtraits?在您致電operator <<時,沒有其他內容可以從中推導出來。

你有兩種解決這個問題的方法。要麼你投的endl類型明確,告訴其超載,應挑選的編譯器:

foo << (std::ostream& (*)(std::ostream&))endl; 

或者,像你一樣,你創建的operator <<一個重載接受與特定簽名的功能。你的編譯器現在會選擇它:

Foo& operator<<(ostream& (*f)(ostream&)) 
{ 
    return *this << f; 
} 

在這個函數的定義是沒有歧義,以什麼f是:它的類型是精確定義。但是,在這裏要小心:這個功能不可能做你期望的!事實上,它只是不斷調用自己,產生一個無限遞歸

因此,這種說法:

[...] note I'm actually calling my other method implementation:

不正確:你不調用其他方法實現,你不停地一遍又一遍地調用同一個功能。

+0

拍攝,很好的抓住,我可能應該跑我的代碼,而不是隻檢查它編譯... – 2013-02-23 16:56:13