使用std::unreachable_sentinel构成范围是否会引发未定义行为?
问题背景
Cppreference给出的std::unreachable_sentinel使用示例如下:
template<class CharT> constexpr std::size_t strlen(const CharT* s) { return std::ranges::find(s, std::unreachable_sentinel, CharT{}) - s; }
但C++标准在[iterator.requirements.general-10]中规定:
A sentinel s is called reachable from an iterator
iif and only if there is a finite sequence of applications of the expression++ithat makesi == s. If s is reachable fromi, [i,s) denotes a valid range.
… [跳过计数范围相关段落,此处不适用] …
The result of the application of library functions to invalid ranges is undefined.
由此引出两个问题:这是否意味着上述代码(即使输入字符串以零结尾)仍存在未定义行为?若确实如此,std::unreachable_sentinel的合法使用场景是什么?
问题解答
1. 示例代码存在未定义行为
是的,哪怕输入字符串以零结尾,这段代码依然属于未定义行为。
原因在于C++标准的明确规定:传给库函数的无效范围会导致未定义行为。示例中的范围[s, std::unreachable_sentinel)本身是无效的——std::unreachable_sentinel的设计就是永远无法通过有限次递增迭代器s到达,因此这个范围不符合“有效范围”的定义。
尽管std::ranges::find会在找到终止符CharT{}时提前返回,但库函数的前置要求是传入的范围必须有效。违反这个前置条件,无论算法是否提前终止,都属于未定义行为。
2. std::unreachable_sentinel的合法使用场景
std::unreachable_sentinel的核心价值是给编译器提供优化提示,前提是你能绝对保证算法不会遍历到这个哨位。合法场景包括:
- 明确知道目标必然存在的查找场景:比如在一个非空且已知包含目标元素的容器中查找,用
std::unreachable_sentinel作为哨位,编译器可以省略原本针对哨位的边界检查,提升执行效率。例如:// 已知arr中必然包含5 int arr[] = {1,2,3,4,5}; auto it = std::ranges::find(std::begin(arr), std::unreachable_sentinel, 5); - 算法必然提前终止的场景:当你能确保算法的终止条件(比如找到目标、满足某个谓词)会在到达哨位前触发,此时
std::unreachable_sentinel仅作为一个“不会被触及”的标记,让编译器做更激进的优化。 - 配合无界迭代器的安全使用:某些自定义迭代器本身没有天然的哨位,但你能保证算法不会无限遍历,此时用
std::unreachable_sentinel来替代哨位,同时依赖业务逻辑保证算法终止。
内容的提问来源于stack exchange,提问作者Nikola Benes

