提问人:slyx 提问时间:11/25/2022 更新时间:11/25/2022 访问量:236
阻止使用右值 std::optional<t<T 初始化 std::optional:reference_wrapper<const >>T>
prevent initializing std::optional<std::reference_wrapper<const T>> with rvalue std::optional<T>
问:
std::reference_wrapper
不能绑定到右值引用以防止指针悬空。
但是,通过组合,似乎可以绑定右值。std::optional
也就是说,是但 是.std::is_constructible_v<std::reference_wrapper<const int>, int&&>)
false
std::is_constructible_v<std::optional<std::reference_wrapper<const int>>, std::optional<int>&&>
true
下面是一个示例:
#include <iostream>
#include <optional>
auto make() -> std::optional<int>
{
return 3;
}
int main()
{
std::optional<std::reference_wrapper<const int>> opt = make();
if (opt)
std::cout << opt->get() << std::endl;
return 0;
}
我预计这段代码会被编译器拒绝,但它编译得很好,并且包含悬空指针。opt
这是标准库的错误吗?或者,由于 C++ 语言规范的某种限制,无法阻止此处悬空指针吗?
如果是标准库的bug,在实现自己的类型时如何修复?optional
这是当前 C++ 规范的限制,你能告诉我这个问题来自哪里吗?
答:
允许代码的原因是,当无法使用 rvalue 引用初始化 的基础对象时,它将回退到将基础对象作为 .optional
const&
事实上,即使是这个代码也是允许的:
auto ref = std::cref(static_cast<const int&>(1));
@Jarod42已经指出了这段代码编译的核心原因,但我将详细阐述一下。
以下两个构造函数模板与此问题相关:std::optional<T>
template <class U>
constexpr optional(const optional<U>& other)
requires std::is_constructible_v<T, const U&>; // 1
template <class U>
constexpr optional(optional<U>&& other);
requires std::is_constructible_v<T, U>; // 2
请注意,上面的 requires-clause 仅用于说明。它们可能不存在于库实现提供的实际声明中。但是,该标准要求构造函数模板 1 和 2 仅在满足相应约束时参与重载解决。std::is_constructible_v
第二个重载将不参与重载解析,因为无法构造自 (表示 类型的 rvalue ),这是旨在防止悬空引用的功能。但是,第一个重载将参与,因为 is 可构造自 (表示类型的左值)。问题在于,当推导并且右值绑定到构造函数参数时,它的右值性在此过程中被“遗忘”。std::reference_wrapper<const int>
int
int
std::reference_wrapper<const int>
const int&
const int
U
std::optional<int>
const optional<U>&
如何在用户定义的模板中避免此问题?我认为这是可能的,但很难。基本思想是,您需要表单的构造函数optional
template <class V>
constexpr optional(V&& other)
requires (is_derived_from_optional_v<std::remove_cvref_t<V>> && see_below) // 3
其中 trait 检测参数类型是 的专用化,还是具有 的明确基类,该基类是 的专用化。然后is_derived_from_optional
optional
optional
- 如果是左值引用,则构造函数 3 具有必须满足上述构造函数 1 的约束的附加约束(其中 是 的元素类型)。
V
U
optional
- 如果不是引用(即参数是右值),则构造函数 3 具有必须满足上述构造函数 2 的约束的附加约束,其中参数为 。
V
const_cast<std::remove_cv_t<V>&&>(other)
假设满足构造函数 3 的约束,则根据重载解决的结果,它将委托给构造函数 1 或 2。(通常,如果参数是右值,则必须使用构造函数 1,因为您不能从右值移动。但是,上述约束将防止这种情况在悬空的情况下发生。构造函数 1 和 2 需要设为私有,并且具有具有私有标记类型的参数,因此从用户的角度来看,它们不会参与重载解析。构造函数 3 可能还需要一堆额外的约束,以便其相对于其他构造函数(未显示)的重载解析优先级不高于构造函数 1 和 2 的重载解析优先级。就像我说的,这不是一个简单的解决方案。const
const
reference_wrapper
评论
std::reference_wrapper
template<class U> constexpr optional(const optional<U>&)
template<class U> constexpr optional(optional<U>&&)
#include <functional>