模板重载 ostream 运算符

6
我将尝试美化输出一个STL容器。我的目标是用定界符分隔打印容器元素。但是我遇到了一些问题。
1. g++与VC++的差异。
ostream& operator<<(ostream& o, const vector<string>& v) {
    copy(v.begin(), v.end(), std::ostream_iterator<string>(o,","));
}

int main()
{

    vector<string> s_v;
    s_v.push_back("one");
    s_v.push_back("two");

    cout << s_v;

}

g++(在mingw32上的gcc版本4.4.0)可以编译它并且运作良好。VC ++(Visual Studio 9)无法编译此代码。

error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'const std::string' (or there is no acceptable conversion)
1>        c:\program files (x86)\microsoft visual studio 9.0\vc\include\ostream(653): could be 'std::basic_ostream<_Elem,_Traits> &std::operator <<<char,std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,const char *)'
1>        with
1>        [

为什么会这样?这段代码是不合法的吗?还是说这只是VC++本身的问题?

2. 未使用的模板变量会导致编译错误。

如果现在我向ostream添加一个模板,像这样(它没有被使用,只是存在)

template <typename T>  // <----- Here
ostream& operator<<(ostream& o, const vector<string>& v) {
    copy(v.begin(), v.end(), std::ostream_iterator<string>(o,","));
}

int main()
{

    vector<string> s_v;
    s_v.push_back("one");
    s_v.push_back("two");

    cout << s_v;

}

gcc无法匹配运算符。

    error: no match for 'operator<<' in 'std::cout << s_v'

and a lot more candidates...

为什么模板没有使用?这会有影响吗?
编辑:问题已解决。我必须返回o;
3. 使用的模板
template <typename T>
ostream& operator<<(ostream& o, const vector<T>& v) {
    copy(v.begin(), v.end(), std::ostream_iterator<T>(o,","));

    return o; // Edited
}

int main()
{

    vector<string> s_v;
    s_v.push_back("one");
    s_v.push_back("two");

    vector<int> i_v;
    i_v.push_back(1);
    i_v.push_back(2);

    cout << s_v;
    cout << i_v;
}

如果我使用模板类型,g++可以编译它,但然后会出现异常。

terminate called after throwing an instance of 'std::bad_cast'
  what():  std::bad_cast

VC++ 只是坐着看 gcc 做所有这些事情,不编译任何内容。

能否有人为我澄清这些事情?谢谢。


在 std 命名空间中定义你的运算符。你会看到区别的。 - PiotrNycz
1
@PiotrNycz 除非涉及到你已经定义过的类型,否则不要在std::中定义运算符。否则,代码是非法的。 - James Kanze
1
@Curious 我无法重现这个错误。一旦我修复了明显的错误(缺少包含文件,缺少 std::,缺少返回值),它就可以在我的系统上运行(VC++ 11)。 - James Kanze
@Curious,这是因为模板函数的重载解析,正如我在第一条评论中所解释的那样。唯一我不确定的是第一个例子,但这可能是因为您缺少了一些包含文件或其他东西。 - zneak
@AndyProwl 正确。很好的观点。除非它们是函数模板的显式特化,否则您甚至不能为现有函数添加额外的重载。 - James Kanze
显示剩余7条评论
2个回答

5

前提:

首先,这段代码是非法的,因为它缺少一个return语句(这很可能是第三个版本引发异常的原因):

ostream& operator<<(ostream& o, const vector<string>& v) {
    copy(v.begin(), v.end(), std::ostream_iterator<string>(o,","));
    return o; // <== THIS ONE WAS MISSING
}

这会向您的程序注入未定义的行为。根据C ++ 11标准第6.6.3 / 1段,事实上:

[...]函数结束时没有返回值相当于没有值的返回; 这会导致值返回函数出现未定义行为

关于您的第一个问题:

一旦修复了这个问题,您的代码就没问题了,并且随VC9一起提供的标准库实现可能存在错误。

实际上,编译器应该在参数命名空间(std)和调用所在的命名空间(全局命名空间)中查找可用的operator <<重载。只要您的运算符定义在全局命名空间中并且语句cout << s_v也在全局命名空间中,重载决议就应该成功选择您的重载。

关于您的第二个问题:

为什么?模板未被使用。应该无所谓才对?

很简单:编译器无法从函数参数推导出T,因此除非您显式指定它,否则这将导致编译错误。然而,明确指定模板参数意味着做类似于以下内容的事情,这几乎是没有意义的:

::operator << <void>(std::cout, s_v);

在C++11中,您可以为T指定一个默认参数,这将使函数调用合法,但是,这有什么目的呢?

关于您的第三个问题:

T在至少一个函数参数的类型中在推导上下文中使用时,编译器将允许从函数参数中推导出它(在这种情况下,它将推导T = std::string,您不必显式指定它。

结论:

综上所述:在添加必要的return语句后,您程序的第一和第三版本是合法的且有意义的,而第二个版本则不是。


关于他在第一个问题中实际发布的代码:由于缺少包含文件,它无法在任何编译器上编译。当缺少包含文件时,我曾经在VC++中看到过奇怪的重载解析:包括<iostream>可能会提供一些(但不是全部)<string>,因此有些东西可以工作,但有些则不能。 - James Kanze
@JamesKanze:没错,我假设OP已经导入了正确的头文件和所有必要的文件,但是由于问题文本中缺少前置部分,这一点无法确定。 - Andy Prowl
我正在使用std;,并且我已经包含了必要的头文件。 - user2237197
问题出在返回值上,然后是第二部分,正如你们两个都解释的那样。 - user2237197
@Curious:第三个版本应该可以处理不同类型的向量。 - Andy Prowl
显示剩余6条评论

1
  1. 根据您发布的代码,任何编译器都无法编译它。您缺少一些头文件和很多std::。我猜测的是,您缺少所有必要的头文件;特别是,缺少#include <string>,而g++间接地拾取了它。这很奇怪,因为问题通常相反:VC++会拾取很多额外的头文件。但有时只拾取部分(因此您可能只知道std::string,但不知道与之关联的非成员函数,如operator<<)。然而,如果没有看到您实际使用的头文件,很难说。

  2. 编译器只能对函数进行重载决策,而不能对函数模板进行。在开始重载决策之前,它尝试使用正确的名称实例化函数模板。成功实例化会产生一个函数,将其添加到重载集中。但是,它如何实例化您的函数模板呢?它无法知道应该使用什么来替换T。因此,它不会实例化(模板参数推导失败),也不会将其实例添加到重载集中。

  3. 我在这里没有看到任何明显的问题。在operator<<中添加缺失的返回语句后,它在VC++上编译并运行正确。


我正在使用std;并且我有正确的包含文件。然而,你用VC可以工作,这很奇怪。但对我来说仍然不行。第三个正确的版本。 - user2237197

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接