调用std::vector中std::function lambda内的std::subrange为何是未定义行为?
问题描述
考虑如下代码,其中定义了存储std::function对象的模板化std::vector类型,这些std::function设计为接收const &形式的范围以及对应的范围迭代器作为入参。代码运行时出现异常:传入该std::function包装的lambda的position(类型为std::ranges::iterator_t)失效,在g++、clang++编译环境下,std::cout的迭代逻辑会越过std::string的末尾边界,需要定位问题原因。
问题复现代码如下:
#include <ranges> #include <string> #include <iostream> #include <vector> #include <functional> int main(const int, const char * []) { auto consume = [](const auto & source, auto position) { auto consumption = std::ranges::subrange(position, std::end(source)); for (auto res : consumption) { std::cout << res; } }; std::vector<std::function<void( std::string, std::string::const_iterator)>> lambdas{consume}; const auto range = std::string("hello"); lambdas.back()(range, std::begin(range)); }
上述代码为精简后的最小可复现示例,启用ASAN的优化版本可稳定检测到越界内存访问。
问题根因
核心错误是std::function的签名定义和lambda的参数语义不匹配:
- 定义的
std::function第一个入参类型为按值传递的std::string,但泛型lambdaconsume的第一个参数是const auto&常量引用。 - 调用包装的lambda时,
std::function会先对传入的原字符串range做一次拷贝,生成一个临时std::string对象,再让lambda的source引用绑定到这个临时对象上。 - 传入的第二个参数
position是从原字符串range上获取的起始迭代器,但lambda内部调用std::end(source)拿到的是临时拷贝字符串的尾迭代器。两个迭代器分属完全独立的std::string实例,用它们构造std::ranges::subrange属于标准明确规定的未定义行为,因此会出现越界遍历、读取非法内存的问题,该问题和编译器实现、优化等级无关。
修复方案
将std::function的第一个参数类型修改为常量引用,和lambda的参数语义保持一致即可:
std::vector<std::function<void( const std::string&, std::string::const_iterator)>> lambdas{consume};
修改后传入的迭代器和范围属于同一个字符串实例,范围构造合法,遍历行为符合预期。
内容的提问来源于stack exchange,提问作者Robbie
相关产品推荐
相关产品推荐

