Недавно я следил за обсуждением 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);
Следовательно, в основном это создает временное и, если это удается, копирует / перемещает его в реальное место.
ИМО это нарушение "Во-первых, уничтожает текущее значение", как указано в документе. Когда я прочитал стандарт, то после a v.emplace(...)
текущее значение в варианте всегда уничтожается, и новый тип является либо установленным типом, либо не имеет значения.
Я понимаю, что условие is_trivially_copyable
исключает все типы, которые имеют наблюдаемый деструктор. Так что это также может быть, как: «как-будто вариант повторно инициализируется со старым значением» или около того. Но состояние варианта является наблюдаемым эффектом. Так действительно ли стандарт позволяет, что emplace
не меняет текущее значение?
Изменить в ответ на стандартную цитату:
Затем инициализирует содержащееся в нем значение, как если бы он выполнял прямую инициализацию без списка значений типа TI с аргументами
std::forward<Args>(args)...
.
T tmp {std::forward<Args>(args)...}; this->value = std::move(tmp);
Действительно ли это считается верной реализацией вышесказанного? Это то, что подразумевается под «как будто»?
источник
might/may
формулировкой, поскольку в стандарте не указывается, что является альтернативой.there is no way to detect the difference
.Да.
emplace
должен обеспечивать основную гарантию отсутствия утечек (то есть соблюдение срока службы объекта, когда строительство и разрушение вызывают наблюдаемые побочные эффекты), но, когда это возможно, разрешается предоставлять надежную гарантию (т. е. исходное состояние сохраняется в случае сбоя операции).variant
должен вести себя аналогично объединению - альтернативы размещаются в одном регионе с соответствующим распределенным хранилищем. Не разрешается выделять динамическую память. Следовательно, изменение типаemplace
не может сохранить исходный объект без вызова дополнительного конструктора перемещения - оно должно уничтожить его и создать новый объект вместо него. Если эта конструкция терпит неудачу, то вариант должен перейти в исключительное бесполезное состояние. Это предотвращает такие странные вещи, как уничтожение несуществующего объекта.Однако для небольших тривиально копируемых типов можно обеспечить надежную гарантию без слишком больших накладных расходов (в данном случае даже повышения производительности для избежания проверки). Поэтому реализация делает это. Это соответствует стандарту: реализация по-прежнему обеспечивает базовую гарантию, как того требует стандарт, просто в более удобной для пользователя форме.
Да, если назначение перемещения не дает видимого эффекта, как в случае тривиально копируемых типов.
источник
std::variant
не имеют причин нарушать это. Я согласен, что это можно сделать более четко в формулировке стандарта, но именно так работают другие части стандартной библиотеки. И к вашему сведению, P0088 был первоначальным предложением.if an exception is thrown during the call toT’s constructor, valid()will be false;
так что это запретило эту «оптимизацию»emplace
в P0088 подException safety