GCC9是否允许避免std::变量的无值状态



我最近关注了Reddit的一次讨论,该讨论对编译器之间的std::visit优化进行了很好的比较。我注意到以下内容:https://godbolt.org/z/D2Q5ED

GCC9和Clang9(我猜它们共享相同的stdlib)都不会生成代码,用于在所有类型都满足某些条件时检查和抛出无值异常。这导致了更好的代码生成,因此我提出了MSVC STL的问题,并给出了以下代码:

template <class T>
struct valueless_hack {
struct tag {};
operator T() const { throw tag{}; }
};
template<class First, class... Rest>
void make_valueless(std::variant<First, Rest...>& v) {
try { v.emplace<0>(valueless_hack<First>()); }
catch(typename valueless_hack<First>::tag const&) {}
}

声明是,这使得任何变体都没有价值,阅读文档应该是:

首先,销毁当前包含的值(如果有)。然后direct初始化包含的值,就像构造的值一样键入带有参数std::forward<Args>(args)....T_I如果抛出异常时,*this可能会变成valueless_by_exception。

我不明白的是:为什么它被称为"可能"?如果整个行动失败,留在旧状态合法吗?因为GCC就是这么做的:

// For suitably-small, trivially copyable types we can create temporaries
// on the stack and then memcpy them into place.
template<typename _Tp>
struct _Never_valueless_alt
: __and_<bool_constant<sizeof(_Tp) <= 256>, is_trivially_copyable<_Tp>>
{ };

后来它(有条件地)做了一些类似的事情:

T tmp  = forward(args...);
reset();
construct(tmp);
// Or
variant tmp(inplace_index<I>, forward(args...));
*this = move(tmp);

因此,基本上它创建了一个临时的,如果成功,则将其复制/移动到实际位置。

国际海事组织这违反了文件规定的"首先,销毁目前包含的价值"。当我阅读标准时,在v.emplace(...)之后,变量中的当前值总是被破坏,新类型要么是集合类型,要么是无值类型。

我确实得到条件is_trivially_copyable排除了所有具有可观察析构函数的类型。因此,这也可以是:"就好像用旧值重新初始化了变量"左右。但变量的状态是一种可观察的影响。那么,标准确实允许emplace不改变当前值吗?

根据标准报价进行编辑:

然后初始化包含的值,就像直接非列表初始化具有参数std​::​forward<Args>(args)...的TI类型的值一样。

T tmp {std​::​forward<Args>(args)...}; this->value = std::move(tmp);真的算是上述内容的有效实现吗?这就是"好像"的意思吗?

我认为标准的重要部分是:

发件人https://timsong-cpp.github.io/cppwp/n4659/variant.mod#12

23.7.3.4修改

(…)

模板variant_alternative_t>&模板(Args&&…Args);

(…)如果在初始化包含的值期间抛出异常,则变体可能不包含值

它说"可能"而不是"必须"。我希望这是有意的,以便允许类似gcc所使用的实现。

正如您自己所提到的,只有当所有备选方案的析构函数都是琐碎的,因此不可观测时,这才有可能,因为需要销毁以前的值。

后续问题:

Then initializes the contained value as if direct-non-list-initializing a value of type TI with the arguments std​::​forward<Args>(args)....

是否执行tmp{std​::​正向(args)…};this->value=std::move(tmp);真的算是上述内容的有效实现吗?这就是"好像"的意思吗?

是的,因为对于一般可复制的类型,没有办法检测差异,所以实现的行为就像值是按照描述初始化的一样。如果该类型不是微不足道的可复制类型,那么这将不起作用。

因此,标准确实允许emplace不更改当前值?

是。emplace应提供不泄漏的基本保证(即在施工和破坏产生可观察到的副作用时尊重物体寿命),但在可能的情况下,允许提供强有力的保证(即操作失败时保持原始状态)。

CCD_ 12的行为必须类似于并集—备选方案被分配在适当分配的存储器的一个区域中。不允许分配动态内存。因此,改变类型的emplace无法在不调用额外的移动构造函数的情况下保留原始对象—它必须摧毁它并构造新的对象来代替它。如果这种构造失败,那么变体必须进入异常的无值状态。这样可以防止诸如破坏不存在的对象之类的奇怪事情。

然而,对于小的、琐碎的可复制类型,可以在没有太多开销的情况下提供强大的保证(在这种情况下,甚至可以提高避免检查的性能)。因此,实施做到了。这是符合标准的:实施仍然提供了标准要求的基本保证,只是以一种更方便用户的方式。

根据标准报价进行编辑:

然后初始化包含的值,如同直接非列表初始化具有参数的TI类型的值std​::​forward<Args>(args)...

T tmp {std​::​forward<Args>(args)...}; this->value = std::move(tmp);真的算是上述内容的有效实现吗?这就是"好像"的意思吗?

是的,如果移动分配没有产生可观察到的效果,那么对于普通的可复制类型就是这样。

最新更新