流操纵器的模板类型推导

Template type deduction for stream manipulators

本文关键字:类型 操纵      更新时间:2023-10-16

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

我正在使用的示例代码:

#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;
}

如果问题不清楚,我会问为什么它一开始就不能推导出模板。

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的哪个实例(这是一个未解决的重载(您可以通过执行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;
}

问题是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(生成的。然后,这些候选函数将以通常的方式作为候选函数进行处理。

您的operator <<被定义为模板,编译器需要推导T的类型。然而,编译器怎么能知道你指的是endl的哪个实例化呢?如何推导出模板参数charTtraits?在您对operator <<的调用中没有其他内容可以从中推断。

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

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

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

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

在这个函数定义中,f是什么没有歧义:它的类型是精确定义的。然而,这里要小心:这个函数不太可能达到你所期望的效果!事实上,它只是不断地调用自己,生成一个无限递归

因此,这个断言:

[…]注意,我实际上正在调用我的另一个方法实现:

不正确:您没有调用另一个方法实现,而是一次又一次地调用同一个函数。