GCC9'ün std :: varyantının değersiz durumundan kaçınılması mümkün mü?


14

Son zamanlarda std::visit, derleyiciler arasında optimizasyonun güzel bir karşılaştırmasına yol açan bir Reddit tartışmasını izledim . Aşağıdakileri fark ettim: https://godbolt.org/z/D2Q5ED

Hem GCC9 hem de Clang9 (sanırım aynı stdlib'i paylaşıyorlar), tüm türler bazı koşulları karşıladığında değersiz bir istisnayı kontrol etmek ve atmak için kod oluşturmazlar. Bu daha iyi kodgen yol açar, bu nedenle MSVC STL ile ilgili bir sorun gündeme getirdi ve bu kod ile sunuldu:

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&) {}
}

İddia şuydu, bunun herhangi bir varyasyonu değersiz hale getirmesi ve dokümanı okuması gerekir:

İlk olarak, mevcut değeri (varsa) yok eder. Ardından, içerilen değeri T_Ibağımsız değişkenlerle std::forward<Args>(args)....bir tür değeri oluşturuyormuş gibi doğrudan başlatır. Bir istisna atılırsa, *thisvalueless_by_exception haline gelebilir.

Ne anlamıyorum: Neden "mayıs" olarak ifade edilir? Tüm operasyon atarsa ​​eski halde kalmak yasal mıdır? Çünkü GCC'nin yaptığı budur:

  // 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>>
    { };

Ve daha sonra (şartlı olarak) şöyle bir şey yapar:

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

Bu temelde bir geçici oluşturur ve bu başarılı olursa kopyalar / gerçek yere taşır.

IMO bu, docu tarafından belirtildiği gibi "İlk olarak, şu anda mevcut olan değeri yok eder" ihlalidir. Standardı okuduğumdan sonra v.emplace(...), varyanttaki mevcut değer her zaman yok edilir ve yeni tip ya ayarlı tip ya da değersizdir.

is_trivially_copyableDurumun gözlenebilir bir yıkıcıya sahip tüm türleri hariç tuttuğunu anlıyorum . Dolayısıyla bu şu şekilde de olabilir: "as-if varyantı eski değerle yeniden başlatıldı" ya da öylesine. Ancak varyantın durumu gözlemlenebilir bir etkidir. Standart gerçekten buna izin veriyor mu, bu emplacemevcut değeri değiştirmiyor mu?

Standart bir teklife yanıt olarak düzenleme:

Ardından, içerilen değeri argümanlarla TI türünde bir değeri doğrudan liste dışı olarak başlatır gibi başlatır std​::​forward<Args>(args)....

T tmp {std​::​forward<Args>(args)...}; this->value = std::move(tmp);Gerçekten yukarıdakilerin geçerli bir uygulaması sayılır mı ? "Sanki" ile kastedilen bu mudur?

Yanıtlar:


7

Ben standardın önemli bir parçası olduğunu düşünüyorum:

Gönderen https://timsong-cpp.github.io/cppwp/n4659/variant.mod#12

23.7.3.4 Değiştiriciler

(...)

şablon variant_alternative_t> & emplace (Args && ... args);

(...) İçerilen değerin başlatılması sırasında bir istisna atılırsa, değişken bir değer taşımayabilir

"Belki" olmaz "der. Gcc tarafından kullanılan gibi uygulamalara izin vermek için bunun kasıtlı olmasını beklerdim.

Kendinizden bahsettiğiniz gibi, bu sadece tüm alternatiflerin yıkıcıları önemsiz ve dolayısıyla gözlemlenemezse mümkündür, çünkü önceki değerin yok edilmesi gerekir.

Takip eden soru:

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

T tmp {std :: ileri (argümanlar) ...}; this-> value = std :: move (tmp); gerçekten yukarıdaki geçerli bir uygulama sayılır? "Sanki" ile kastedilen bu mudur?

Evet, çünkü önemsiz bir şekilde kopyalanabilen tipler için , farkı tespit etmenin bir yolu yoktur, bu nedenle uygulama, değerin açıklandığı gibi başlatıldığı gibi davranır. Tür önemsiz bir şekilde kopyalanamazsa bu çalışmaz.


İlginç. Soruyu bir takip / açıklama talebiyle güncelledim. Kök: Kopyalamaya / taşımaya izin veriliyor mu? might/mayStandart alternatifin ne olduğunu belirtmediği için ifadelerle çok kafam karıştı .
Alev ateşi

Bu standart teklif ve için kabul there is no way to detect the difference.
Alev ateşi

5

Standart gerçekten buna izin veriyor mu, bu emplacemevcut değeri değiştirmiyor mu?

Evet. emplacesızıntı olmamasının temel garantisini sağlayacaktır (yani, inşaat ve yıkım gözlemlenebilir yan etkiler ürettiğinde nesnenin ömrüne saygı gösterilmesi), ancak mümkün olduğunda güçlü garantinin sağlanmasına izin verilir (yani, bir işlem başarısız olduğunda orijinal durum korunur).

variantbir sendikaya benzer şekilde davranması gerekir - alternatifler uygun şekilde tahsis edilmiş depolamanın bir bölgesinde tahsis edilir. Dinamik bellek ayrılmasına izin verilmiyor. Bu nedenle, tür değiştirmenin emplaceorijinal nesneyi ek bir hareket yapıcısı çağırmadan saklamanın bir yolu yoktur - onu yok etmek ve yeni nesneyi onun yerine inşa etmek zorundadır. Bu yapı başarısız olursa, varyant istisnai değersiz duruma gitmelidir. Bu, varolmayan bir nesneyi yok etmek gibi garip şeyleri önler.

Bununla birlikte, önemsiz derecede kopyalanabilen küçük tipler için, çok fazla ek yük olmadan güçlü garanti sağlamak mümkündür (bu durumda, bir kontrolden kaçınmak için performans artışı bile). Bu nedenle, uygulama bunu yapar. Bu standartlara uygundur: uygulama, standardın gerektirdiği şekilde, daha kullanıcı dostu bir şekilde temel garantiyi sağlar.

Standart bir teklife yanıt olarak düzenleme:

Ardından, içerilen değeri argümanlarla TI türünde bir değeri doğrudan liste dışı olarak başlatır gibi başlatır std​::​forward<Args>(args)....

T tmp {std​::​forward<Args>(args)...}; this->value = std::move(tmp);Gerçekten yukarıdakilerin geçerli bir uygulaması sayılır mı ? "Sanki" demekle mi kastediliyor?

Evet, taşıma ataması gözlemlenebilir bir etki oluşturmazsa, bu önemsiz şekilde kopyalanabilir tipler için geçerlidir.


Mantık mantığına tamamen katılıyorum. Bunun aslında standartta olduğundan emin değilim? Bunu herhangi bir şeyle destekleyebilir misin?
Alev ateşi

@Flamefire Hmm ... Genel olarak, standart işlevler temel garantiyi sağlar (kullanıcının sağladığı şeyle ilgili bir sorun olmadığı sürece) ve std::variantbunu kırmak için bir neden yoktur. Bunun standardın ifadesinde daha açık hale getirilebileceğini kabul ediyorum, ancak temel olarak diğerleri standart kütüphanenin bir parçası olarak çalışıyor. Ve FYI, P0088 ilk teklifti.
LF

Teşekkürler. İçinde daha açık bir spesifikasyon var: if an exception is thrown during the call toT’s constructor, valid()will be false;Yani bu "optimizasyonu" yasakladı
Flamefire

Evet. Şartname emplaceP0088 içinde altındaException safety
Flamefire

@Flamefire Orijinal teklif ile oylanan sürüm arasında bir tutarsızlık var gibi görünüyor. Son sürüm "mayıs" ifadesine dönüştü.
LF
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.