auto item-declaration in Range-Based For Loop on a const
在C++ Primer里有一道练习题:
Exercise 3.11: Is the following range for
legal? If so,
what is the type of c
? 1
2const string s = "Keep out!";
for (auto &c : s) { /* ... */ }
直觉上c
的类型应该是const char&
,但是怎么推理得到c
的类型呢?细究起来确实花了一番功夫。
使用auto声明的变量必须要给初始值,而这里的语法没有给初始值。Range-Based for loop应该是一种语法糖,实际上编译器应该是当成普通的for循环来处理的。
从cppreference(https://en.cppreference.com/w/cpp/language/range-for)上可以得到印证。 Range-Based for loop的一般形式(省略了不相关的部分)实际上等价于下面的for循环:
for ( item-declaration : range-initializer ) statement
item-declaration
- a declaration for each range itemrange-initializer
- an expression or brace-enclosed initializer liststatement
- any statement (typically a compound statement)
The above syntax produces code equivalent to the following (the variables and expressions wrapped in
/* */
are for exposition only):
1
2
3
4
5
6
7
8
9 {
auto&& /* range */ = range-initializer ;
for (auto /* begin */ = /* begin-expr */, /* end */ = /* end-expr */;
/* begin */ != /* end */; ++/* begin */)
{
item-declaration = */* begin */;
statement
}
}
Exposition-only expressions
/* begin-expr */
and/* end-expr */
are defined as follows:If the type of
/* range */
is a reference to a class type C, and searches in the scope of C for the names “begin” and “end” each find at least one declaration, then/* begin-expr */
is/* range */.begin()
and/* end-expr */
is/* range */.end()
.
在习题里,range-initializer
对应的是s
,item-declaration
是auto &c
。s
类型是std::string
,是class type
,而std::string
也确实定义了begin
和end
函数,所以/* begin-expr */
和/* end-expr */
就是string::begin()
和string::end()
。
那么string::begin()
的返回值就决定了c
的类型。cppreference(https://en.cppreference.com/w/cpp/string/basic_string/begin)可以看到函数签名const_iterator begin() const;
。因为s
是const string
,所以我们关注的是const
函数。
根据cppreference(https://en.cppreference.com/w/cpp/string/basic_string,https://en.cppreference.com/w/cpp/named_req/Iterator),const_iterator
是一种LegacyIterator
,而iterator可以被看作是指针的一种抽象。
LegacyRandomAccessIterator
andLegacyContiguousIterator
toconst value_type
Iterators can be thought of as an abstraction of pointers.
cppreference
LegacyRandomAccessIterator
的页面(https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator)提到,如果iterator是来自一个container,那dereference这个iterator得到的object的类型就是containter的value_type
。
If a LegacyRandomAccessIterator it originates from a Container, then it's value_type is the same as the container's, so dereferencing (*it) obtains the container's value_type.
那么string是不是一个Container呢?cppreference说它满足AllocatorAwareContainer、SequenceContainer、ContiguousContainer的要求,那么我们也可以说string是一种container。
std::basic_string satisfies the requirements of
AllocatorAwareContainer
(except that customized construct/destroy are not used for construction/destruction of elements),SequenceContainer
andContiguousContainer(since C++17)
.
(不过LegacyContiguousIterator
(https://en.cppreference.com/w/cpp/named_req/ContiguousIterator)没有提到dereference该怎么办,或者是我看漏了,这里也只能先忽略这种情况了)
const_iterator
指向的是const value_type
。std::string
实际上是std::basic_string<char>
,而value_type
就是char
。所以对const_iterator
dereference(*it
)的话得到的object类型是const char
。
一般情况下,auto
会忽略top-level
consts。但是如果用auto
声明一个reference,那top-level
const
就不会被忽略了。习题的情况正是后者,所以c
的类型是const char&
。