Нещодавно я спостерігав за обговоренням 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&) {}
}
Стверджувалося, що це робить будь-який варіант безцінним, і читати документ він повинен:
По-перше, знищує міститься в даний час значення (якщо воно є). Тоді пряма ініціалізація містив значення, як ніби будує значення типу
T_I
з аргументами.std::forward<Args>(args)....
Якщо виняток викидається,*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);
Отже, в основному він створює тимчасовий характер, і якщо це вдасться копіювати / переміщує його на справжнє місце.
IMO це порушення "По-перше, знищує значення, що міститься в даний момент", як заявляє документ. Коли я читаю стандарт, то після v.emplace(...)
поточного значення у варіанті завжди руйнується, а новий тип або заданий тип, або безцінний.
Я розумію, що умова is_trivially_copyable
виключає всі типи, у яких спостерігається деструктор. Тож це може бути хоч як: "варіант-as-if реініціалізується зі старим значенням" чи так. Але стан варіанту - ефект, що спостерігається. Тож чи дійсно стандарт дозволяє, що emplace
не змінює поточне значення?
Редагувати у відповідь на стандартну цитату:
Потім ініціалізує міститься значення, як ніби прямо-не-список - ініціалізує значення типу TI з аргументами
std::forward<Args>(args)...
.
Чи T tmp {std::forward<Args>(args)...}; this->value = std::move(tmp);
дійсно вважається дійсною реалізацією вищезазначеного? Це те, що означає "наче"?
might/may
формулювання, оскільки стандарт не вказує, що таке альтернатива.