6.注意C++的分析机制

假设我们有一个存有整数的文件,并试图把它们复制到一个list中,我们也许会写下

1
2
fstream dataFile("ints.dat");
list<int> data(istream_iterator<int>(dataFile), istream_iterator<int>());

这里的想法是传一对istream_iterator给list的区间构造函数,尽管它不会发生编译错误(还不如发生),但运行效果和期望的相差很大。准确的说,它并没有声明一个list,也没有构造。

我们先从简单的部分开始解释,在c++中,以下三种声明均是合法且等价的:

1
2
3
int f(double d);
int f(double (d));
int f(double);

接着看三个函数声明:
1
2
3
4
5
6
//g是一个返回值为int的函数,其形参是一个函数指针,该指针指向一个不需要形参且返回值为double的函数
int g(double (*pf)());
//同上,pf其实是一个函数指针
int g(double pf());
//同上,省略了参数名
int g(double ());

请注意围绕参数名的括号,比如第一组第二个的(d)与独立的括号的区别。围绕参数名的括号可以被忽略,而独立的括号则表明参数列表的存在,它说明前面存在一个函数指针参数。

返回到问题,我们仔细观察这一行语句:

1
list<int> data(istream_iterator<int>(dataFile), istream_iterator<int>());

这其实是声明了一个函数,其返回值是list<int>,函数名为data,接受两个参数:

第一个参数名为dataFile,是一个迭代器。(认为dataFile两侧括号多余)
第二个参数没有名称,它的类型是指向一个没有参数而且返回istream_iterator的函数的指针

顺便一提,

1
2
class Widget {...}; // 假设Widget有默认构造函数
Widget w();

这种东西自然是不能构造对象的,因为它其实是声明了一个不接受参数且返回一个Widget的函数。

为了解决我们遇到的这种烦人的分析机制,我们可以给迭代器名字:

1
2
3
4
ifstream dataFile("ints.dat");
istream_iterator<int> dataBegin(dataFile);
istream_iterator<int> dataEnd;
list<int> data(dataBegin, dataEnd);

尽管这种命名式实参的风格与STL相违背,但是为了避免二义性并增加可读性,这不失为一种优雅的解决方式。