C++ProgramlamaC++ Geliştirici

C++20'nin, tür olmayan şablon argümanları olarak kullanılan kayan nokta değerleri arasında eşitliği belirlemek için uyguladığı belirli bit düzeyinde karşılaştırma kuralı nedir ve neden -0.0 ve +0.0 çalışma zamanı ifadelerinde eşit olmasına rağmen farklı şablon örnekleri oluşturur?

Hintsage yapay zeka asistanı ile mülakatları geçin

Soruya cevap

C++20, kayan nokta türlerini tür olmayan şablon parametreleri (NTTP'ler) olarak tanıtarak onları yapısal türler olarak sınıflandırdı. Standart ([temp.type]/4) gereğince, iki tür olmayan şablon argümanı yalnızca eşit olduklarında eşleşir. Kayan nokta değerleri için eşitlik, değer eşitliği yerine bit düzeyindeki tanımlamaya göre belirlenir. Bu, iki kayan nokta sabitinin yalnızca kimlik nesne temsilleri (her bit eşleşmelidir) aynı olduğunda aynı şablon argümanı olarak kabul edileceği anlamına gelir.

Sonuç olarak, yalnızca işaret bitlerinde farklılık gösteren +0.0 ve -0.0, farklı şablonlar oluşturur. Benzer şekilde, farklı NaN yükleri de farklı türler oluşturur. Bu, çalışma zamanı davranışı ile belirgin bir şekilde zıttır; burada +0.0 == -0.0 ifadesi true olarak değerlendirilir çünkü eşitlik operatörü matematiksel eşitliği uygular, ancak şablon mekanizması fiziksel kimliği gerektirir.

Hayattan bir durum

Bunu, bir fizik simülasyon motoru için bir derleme zamanı boyutsal analiz kütüphanesi geliştirirken karşılaştık. Fiziksel sabitleri (örneğin, yerçekimi sabitleri) temsil etmek için double NTTP'leri kullandık ve sıfır kütlesinin teorik durumu için çözücülere özelleşmiş olmak istedik (bu, 0.0 ile temsil edilir). Ancak, bazı constexpr hesaplamaları, kütle merkezi değerlendirmesi -1.0 * 0.0 gibi belirli aritmetik işlemler aracılığıyla -0.0 üretti.

Kullanıcılar, bu hesaplamaların sonucunu bir şablon argümanı olarak geçtiklerinde, derleyici, ZeroMass özel çözümlememiz yerine genel uygulamayı seçti, bu da genel versiyonun tam matris terslemeleri yapması nedeniyle %40'lık bir performans gerilemesine yol açtı.

Üç çözüm düşündük. İlk olarak, hem +0.0 hem de -0.0 için açıkça özelleşebiliriz. Bu yaklaşım doğru davranışı garanti ediyordu ama bakım yükümüzü iki katına çıkardı ve hala çeşitli NaN temsillerini veya farklı bit desenleri olan ama yuvarlama hataları nedeniyle etkin olarak sıfıra yakın değerleri karşılayamadı.

İkinci olarak, her girdiyi, işaret bitini sıfıra zorlayan bir constexpr yardımcı fonksiyonu kullanarak normalize etmeyi düşündük (örneğin, value == 0.0 ? 0.0 : value). Bu çözüm sıfırlar için sağlamdı ama her şablon örneklemesi etrafında sarıcı makroları gerektiriyordu, bu da API'yi kirletti ve doğrudan parametre geçişini bekleyen kullanıcıları karıştırdı.

Üçüncü olarak, if constexpr ve std::bit_cast kullanarak bir tür normalizasyon katmanı uyguladık, değerleri meta-fonksiyonlarımızın giriş noktasında kanonikleştirdik ve tüm sıfırları pozitif olarak ele alıp geçersiz NaN'ları kanonik bir yüke dönüştürdük. Bu çözümü seçtik çünkü kullanıcılar için şeffaflık sağlarken iç tutarlılığı garanti ediyordu.

Uygulamadan sonra, kütüphanenin tüm kayan nokta NTTP'lerini bit temsiline göre ele aldığını belgeledik. Bu, performans sorunlarını çözdü, ancak geliştiricilerin -0.0 ve +0.0'ın tür sisteminde ayrı yapılandırma durumları olduğunu bilmesini gerektirdi.

Adayların sıklıkla atladığı noktalar

Neden std::is_same_v<decltype(func<+0.0>()), decltype(func<-0.0>())> false değerlendirirken +0.0 == -0.0 true'dur?

Şablon örneklemesi, Tek Tanım Kuralı ve tam şablon argüman eşleşmesine dayanır. Derleyici func<+0.0>() ile karşılaştığında kayan nokta literalinin bit desenini hashler veya karşılaştırır. IEEE 754'ün, -0.0'ın işaret bitinin ayarlandığını belirtmesi nedeniyle derleyici iki farklı sabit değeri görüyor ve iki ayrı işlev örneklemesi üretiyor. Çalışma zamanı eşitlik operatörü, işaretli sıfırların karşılaştırılmasını sağlarken, şablon mekanizması çalışma zamanı anlamsalardan önce nesne temsili düzeyinde işlem yapar. Adaylar, değerlerin matematiksel olarak eşdeğer olduğunu düşündüklerinden aynı türü üretmeleri gerektiğini varsayıyor, bu da çalışma zamanı değeri anlamsalını derleme zamanı tür kimliği ile karıştırıyor.

Neden template<float F> struct S{}; S<1.0> normal ifadelerde 1.0'ın float'a dönüştürülebilir olmasına rağmen derlenemez?

Kayan nokta türündeki tür olmayan şablon parametreleri için, C++20 standardı, şablon argümanının parametre ile tam aynı türde olmasını açıkça gerektirir; standart kayan nokta terfileri ve dönüşümleri yasaklanmıştır ([temp.arg.nontype]/5). Literal 1.0 türü double olduğu için doğrudan float F ile bağlanamaz. float son ekini kullanmalısınız: S<1.0f>. Bu kısıtlama, şablon karıştırma ve tür kimliğinin belirgin temsil gereksinimleri nedeniyle vardır. Yeni başlayanlar genellikle bunu atlar çünkü işlev çağrıları dönüşüme izin verir, ancak şablonlar dönüşüm kuralları dikkate alınmadan önce tam tür eşleşmesi yapar.

Farklı geçerli NaN (qNaN) yükleri, "bir sayı değil" temsil etmesine rağmen şablon örneklemesini nasıl etkiler?

IEEE 754, NaN değerlerinin yük bitlerini (tanımlayıcı bilgileri) taşımasına izin verir. C++20 şablon eşitliği bit düzeyinde karşılaştırma kullandığı için, farklı yükleri olan iki NaN (örneğin, std::numeric_limits<double>::quiet_NaN() ile farklı donanımlarda 0.0/0.0 sonucu) farklı şablon argümanlarıdır. Bu, birden fazla NaN bit düzeni için şablonları oluşturma yollarında kod şişkinliğine yol açabilir veya programcının tek bir özel durum olarak varsaydığı farklı çeviri birimlerinde farklı NaN temsilleri gözlemlendiğinde ince ODR ihlallerine yol açabilir. Adaylar genellikle NaN'ın nullptr gibi tekil bir değer olduğunu varsayıyor, ancak aslında her biri şablon sisteminde farklı olan bir dizi bit desenini temsil eder.