2013-02-23 88 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