5

我不确定这段代码是否不会编译。

我正在使用的示例代码:

#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’

我很困惑为什么它不能用endl( ostream& (*)(ostream&))的函数类型代替T,当你指定时它显然可以这样做cout << endl;

我发现这解决了问题也令人费解[已编辑]

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

如果问题不清楚,我问为什么它不能首先推断出模板。

4

2 回答 2

4

endl是一个操纵器,即它是一个未解析的函数类型。有几个重载,类型推导无法决定你想要哪一个。

更具体地说,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您可以通过执行 a 来解决此问题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;
}
于 2013-02-23T16:40:56.097 回答
2

问题在于endl将操纵器定义为函数模板。C++11 标准的第 27.7.1 段指定了它的签名:

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

此外,根据第 13.3.1 段关于重载解决方案:

在候选函数模板的每种情况下,候选函数模板特化是使用模板参数推导(14.8.3、14.8.2)生成的。然后以通常的方式将这些候选函数作为候选函数处理。

Youroperator <<被定义为模板,编译器需要推导出T. 但是,编译器如何知道endl您的意思是哪个实例化?它如何推断模板参数charTtraits?在你的调用中没有其他operator <<可以推断出来的东西。

你有两种方法可以解决这个问题。要么endl显式地转换类型,以告诉编译器应该选择哪个重载:

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

或者,正如您所做的那样,您创建一个operator <<接受具有该特定签名的函数的重载。您的编译器现在将选择它:

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

在这个函数定义中,关于什么是没有歧义f的:它的类型是精确定义的。但是,在这里要小心:这个函数不可能达到你的预期!事实上,它只是不断地调用自己,产生无限递归

因此,这个断言:

[...] 请注意,我实际上是在调用我的其他方法实现:

正确:您没有调用其他方法实现,而是一遍又一遍地调用相同的函数。

于 2013-02-23T16:54:20.187 回答