为何C++中数组与std::span均可适配基于范围的for循环?
问题:数组为何能在含基于范围for循环的模板函数中生效?
我在尝试C++20时遇到了一个困惑场景:我编写了含基于范围的for循环的模板print函数,原以为该函数仅支持可迭代类型,因此认为传入数组arr时无法运行,仅std::span能生效,但实际两者都成功输出结果,想了解原因。
代码片段如下:
#include <iostream> #include <span> using namespace std; struct simpleType { simpleType(const int id) : id(id) {} int id; }; template <typename IterableType> void print(const IterableType& sequenceToPrint) { cout << "[ "; for(const auto& element : sequenceToPrint) { cout << element.id << " "; } cout << "]" << endl; } void test() { simpleType arr [] {1,2,3,4,5}; span arrSpan {arr}; // 这个应该可以运行 print(arrSpan); // 输出 [ 1 2 3 4 5] // 这个应该不行 print(arr); // 同样输出 [ 1 2 3 4 5 ] } int main() { test(); }
我原本认为这两种情况不会都生效,因为我理解中的模板print函数因基于范围的for循环仅支持可迭代类型,我忽略了什么?
解答
你忽略了C++标准中基于范围的for循环对数组的原生支持,以及模板参数推导时数组类型不会退化为指针的特性:
模板参数推导会保留数组的完整类型:当数组作为模板函数的参数传递时,推导得到的
IterableType是数组类型(比如simpleType[5]),而非指针。只有在非模板函数中传递数组时,才会发生数组到指针的隐式转换。基于范围的for循环对数组有专门处理:对于固定大小的数组,编译器不需要依赖
begin()/end()成员函数,它会直接用数组的首地址和首地址+数组大小来构建迭代范围,展开后的代码类似:auto __begin = arr; auto __end = arr + 5; // 5是数组的编译期固定大小 for (auto __it = __begin; __it != __end; ++__it) { const auto& element = *__it; cout << element.id << " "; }std::span作为连续内存的视图类型,本身就提供了符合要求的begin()和end()成员函数,自然能被范围for循环支持,这和你最初的预期一致。
总结来说,数组是C++范围for循环的原生支持类型之一,加上模板推导不会让数组退化为指针,所以你的print函数传入数组时完全能正常工作。
内容的提问来源于stack exchange,提问作者Emiliano Toledo
相关产品推荐
相关产品推荐

