std::string_view{ nullptr, 0 } 在 C++ 中是否合法?
结论
std::string_view{nullptr, 0} 是合法的。按照现行 C++ 标准,它是 well-formed 的,行为也有明确定义。
这个表达式会构造一个空的 std::string_view:
#include <cassert>
#include <string_view>
int main() {
std::string_view sv{nullptr, 0};
assert(sv.empty());
assert(sv.size() == 0);
assert(sv.data() == nullptr);
}
[nullptr, nullptr) 是有效的空范围。如果写成 std::string_view{nullptr, n},则只有 n == 0 时才满足构造函数的前置条件。
为什么这是 valid range
该构造函数要求:
[str, str + len) is a valid range
当 str == nullptr 且 len == 0 时,需要考虑指针运算和空范围这两点。
nullptr + 0 的结果
指针算术规则明确规定,空指针值加 0 后仍是空指针值。因此:
static_cast<char const*>(nullptr) + 0
结果仍为 nullptr,这个表达式不会产生未定义行为。
这条规则仅适用于加数为 0 的情况。nullptr + 1 一类运算并不成立。
空范围不需要递增迭代器
当 first == last 时,范围 [first, last) 为空。判断 last 能否从 first 到达时,可以执行零次递增操作。
这里的两个端点都是 nullptr:
[nullptr, nullptr)
两者从一开始就相等,因此这是有效的空范围。整个过程不需要递增或解引用空指针,构造函数的前置条件也就得到了满足。
哪些情况不合法
长度不为零时,空指针无法表示相应的字符范围:
std::string_view bad{nullptr, 1}; // 不满足前置条件
这段代码要求形成并访问一个从空指针开始、长度为 1 的范围,但空指针并不指向字符数组。程序违反了库函数的前置条件,行为未定义。
规则可以概括为:
ptr == nullptr && count == 0 // 可以构造空 string_view
ptr == nullptr && count != 0 // 不满足前置条件
P2166 删除的是什么
P2166 处理的是只接收一个指针参数的构造方式,例如:
std::string_view sv{nullptr};
这种构造会把参数视为以空字符结尾的字符串,并计算它的长度。空指针不指向这样的字符串,因此不能用于这种构造。C++23 增加了一个删除的重载,让编译器可以直接发现这类错误:
basic_string_view(nullptr_t) = delete;
带长度的构造函数并未被禁止:
std::string_view sv{nullptr, 0}; // 仍然合法
这个版本明确表示一个长度为零的范围,所以标准保留了它。
“citation source is the same” 指什么
这不是标准中的规范性措辞,而是提案说明中的交叉引用。它沿用前文引用的构造函数规范,也就是 basic_string 和 basic_string_view 为指针加长度构造函数规定的 valid range 前置条件。
相关内容包括:
basic_string的指针加长度构造函数规定[string.view.cons]中basic_string_view(const charT*, size_type)的规定- valid range 和指针算术的一般定义
不存在另一条特殊规则规定空指针加零必然非法。提案只是复用了前文的引用,没有改变规范的含义。
“legal, yet not legitimate” 应该怎样理解
“legal” 表示这种写法符合标准。“not legitimate” 不是 C++ 标准术语,也不产生额外的规范约束,只是提案作者对这种写法的设计评价。
直接写:
std::string_view{nullptr, 0}
通常不如默认构造清楚:
std::string_view{}
前一种写法可能让读者怀疑代码误传了空指针,也可能掩盖上游接口使用“空指针加零长度”表示无数据的事实。因此,提案把它称为合法但不理想的写法。
实际场景中,空指针加零长度并非总是不合理。许多底层接口、C API 和缓冲区类型会用下面的组合表示空数据:
data == nullptr
size == 0
从这类接口构造 std::string_view 是正常需求。如果直接删除带长度的 nullptr_t 重载,还会出现一个细节问题:字面量 nullptr 会被禁止,但先转换为 const char* 的空指针仍能调用原构造函数。这样的限制无法完整表达真正的前置条件。
X 示例同样满足前置条件
将示例补全,并明确使用标准头文件和限定名称:
#include <cstddef>
#include <string_view>
struct X {
operator char const*() const {
return nullptr;
}
private:
friend std::size_t len(X const&) {
return 0;
}
};
int main() {
X const x{};
std::string_view sv{x, len(x)};
}
调用构造函数时,x 被转换为值为 nullptr 的 const char*,而 len(x) 返回 0。构造函数最终接收到的参数等同于:
std::string_view{static_cast<char const*>(nullptr), 0}
这个调用满足 valid range 前置条件,构造出的也是空 std::string_view。
实际代码需要保证指针与长度始终一致。可以先检查这个约束:
#include <cassert>
#include <cstddef>
#include <string_view>
std::string_view make_view(char const* ptr, std::size_t count) {
assert(ptr != nullptr || count == 0);
return {ptr, count};
}
如果接口可能返回 nullptr 和非零长度,应将其视为接口错误,并在构造 std::string_view 前处理。
使用空视图时的注意事项
std::string_view 可以表示 data() == nullptr && size() == 0。但将 data() 传给其他 API 前,仍要检查该 API 自身的前置条件。视图为空,并不意味着所有接收指针的函数都允许空指针:
std::string_view sv{nullptr, 0};
// 错误思路:strlen 要求参数指向以空字符结尾的字符串。
// std::strlen(sv.data());
空视图不一定有可读取的终止字符,因此不能访问 sv[0],也不能直接把 sv.data() 当作有效的 C 字符串。
如果只需要普通的空视图,优先写:
std::string_view sv{};
如果指针和长度来自外部接口,可以接受 nullptr 与零长度的组合,同时应明确检查“空指针只能对应零长度”这一约束。
备注:内容仅供参考。
