流操纵器的模板类型推导

时间:2013-02-23 16:32:16

标签: c++ templates iostream manipulators

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

我正在使用的示例代码:

#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,当你指定{{1}时,它显然可以做到这一点}}

我发现另外令人费解的是,这解决了问题[编辑]

cout << endl;

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

2 个答案:

答案 0 :(得分: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 实例(它是未解决的超载)。 您可以通过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;
}

答案 1 :(得分: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)。然后以通常的方式将这些候选人作为候选职能处理。

您的operator <<被定义为模板,编译器需要推导出T的类型。但是,编译器如何知道您所指的endl实例化?如何推断模板参数charTtraits?您致operator <<的电话中没有其他内容可以从中推断出来。

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

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

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

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

在这个函数定义中,f是什么没有歧义:它的类型是精确定义的。但是,请注意:此功能不太可能达到预期效果!事实上,它只是一直在调用自己,产生无限递归

因此,这个断言:

  

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

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