C ++ derleme zaman sayaçları, yeniden ziyaret edildi


28

TL; DR

Bu yazının tamamını okumaya çalışmadan önce şunu bilin:

  1. sunulan konuya bir çözüm buldum , ancak yine de analizin doğru olup olmadığını bilmek için sabırsızlanıyorum;
  2. Çözümü fameta::counterkalan birkaç tuhaflığı çözen bir sınıfa paketledim . Sen edebilirsiniz github bulmak ;
  3. En görebilirsiniz Godbolt çalışma .

Her şey nasıl başladı

Filip Roséen keşfettiğinden / icat ettiğinden, 2015'te zaman sayaçlarını derleyen kara büyü C ++ ' da, cihaza hafifçe takıntılıydım, bu yüzden CWG işlevselliğin gitmesi gerektiğine karar verdiğinde hayal kırıklığına uğradım, ancak yine de zihinlerinin birkaç zorlayıcı kullanım durumu göstererek değiştirilebilir.

Sonra, birkaç yıl önce uberswitch es yuvalanabilir - ilginç bir kullanım örneği, bence - sadece yeni sürümleriyle daha fazla işe yaramayacağını keşfetmek için Konu 2118 açık durumda olmasına rağmen (ve hala ) mevcut derleyiciler : kod derlenecekti, ancak sayaç artmayacaktı.

Sorun Rosen'in web sitesinde ve son zamanlarda stackoverflow'ta da bildirildi : C ++ derleme zamanı sayaçlarını destekliyor mu?

Birkaç gün önce sorunları tekrar çözmeye çalıştım

Görünüşte hala geçerli olan C ++, artık çalışmayan derleyicilerde nelerin değiştiğini anlamak istedim. Bu amaçla, birisinin bu konuda konuşması için geniş ve uzak web'i aradım, ancak boşuna değil. Bu yüzden denemeye başladım ve bazı sonuçlar elde ettim, burada sunuyorum, burada kendimden daha bilgili olandan bir geri bildirim almak umuduyla.

Aşağıda açıklık sağlamak için Roséen'in orijinal kodunu sunuyorum. Nasıl çalıştığına dair bir açıklama için lütfen web sitesine bakın :

template<int N>
struct flag {
  friend constexpr int adl_flag (flag<N>);
};

template<int N>
struct writer {
  friend constexpr int adl_flag (flag<N>) {
    return N;
  }

  static constexpr int value = N;
};

template<int N, int = adl_flag (flag<N> {})>
int constexpr reader (int, flag<N>) {
  return N;
}

template<int N>
int constexpr reader (float, flag<N>, int R = reader (0, flag<N-1> {})) {
  return R;
}

int constexpr reader (float, flag<0>) {
  return 0;
}

template<int N = 1>
int constexpr next (int R = writer<reader (0, flag<32> {}) + N>::value) {
  return R;
}

int main () {
  constexpr int a = next ();
  constexpr int b = next ();
  constexpr int c = next ();

  static_assert (a == 1 && b == a+1 && c == b+1, "try again");
}

Hem g ++ hem de clang ++ latest-ish derleyicileri ile next()her zaman 1 değerini döndürür. Biraz denedikten sonra, en azından g ++ ile ilgili sorun, derleyici işlev şablonlarını ilk kez çağırdığında işlev şablonlarını varsayılan parametrelerini değerlendirdikten sonra, bu işlevler varsayılan parametrelerin yeniden değerlendirilmesini tetiklemez, bu nedenle yeni işlevler asla başlatılmaz, ancak her zaman daha önce başlatılmış olanlara başvurulur.


İlk sorular

  1. Bu tanıma gerçekten katılıyor musunuz?
  2. Evet ise, bu yeni davranış standart tarafından zorunlu tutulur mu? Birincisi hata mıydı?
  3. Değilse, sorun nedir?

Yukarıdakileri akılda tutarak, bir çalışma buldum: her next()çağrıyı monoton olarak artan benzersiz bir kimlikle işaretleyin, callees'e geçmek, böylece hiçbir çağrı aynı olmayacak, böylece derleyiciyi tüm argümanları yeniden değerlendirmeye zorlayacak her seferinde.

Bunu yapmak bir yük gibi görünüyor, ama bunu düşünmek sadece bir işlev benzeri makroda gizlenmiş standart __LINE__veya- __COUNTER__benzeri (mümkün olan her yerde) makroları kullanabilir counter_next().

Bu yüzden, daha sonra konuşacağım sorunu gösteren en basit biçimde sunduğum aşağıdakileri buldum.

template <int N>
struct slot;

template <int N>
struct slot {
    friend constexpr auto counter(slot<N>);
};

template <>
struct slot<0> {
    friend constexpr auto counter(slot<0>) {
        return 0;
    }
};

template <int N, int I>
struct writer {
    friend constexpr auto counter(slot<N>) {
        return I;
    }

    static constexpr int value = I-1;
};

template <int N, typename = decltype(counter(slot<N>()))>
constexpr int reader(int, slot<N>, int R = counter(slot<N>())) {
    return R;
};

template <int N>
constexpr int reader(float, slot<N>, int R = reader(0, slot<N-1>())) {
    return R;
};

template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N>())+1>::value) {
    return R;
}

int a = next<11>();
int b = next<34>();
int c = next<57>();
int d = next<80>();

Tembellikler için ekran görüntüsü aldığım godbolt'ta yukarıdaki sonuçları gözlemleyebilirsiniz .

resim açıklamasını buraya girin

Gördüğünüz gibi , gövde g ++ ve clang ++ ile 7.0.0'a kadar çalışıyor! , sayaç beklendiği gibi 0'dan 3'e yükselir, ancak clang ++ sürüm 7.0.0'ın üzerinde değildir .

Yaralanmaya hakaret eklemek için, aslında sayaca aslında bu bağlama bağlı olduğu ve bu şekilde Potansiyel olarak sonsuz sayıda sayaç kullanma olanağı sunan yeni bir bağlam tanımlandığında yeniden başlatılabilir. Bu varyantla, 7.0.0 sürümünün üzerindeki clang ++ çökmez, ancak yine de beklenen sonucu üretmez. Godbolt üzerinde yaşa .

Neler olup bittiğine dair herhangi bir ipucu kaybolduğunda , şablonların nasıl ve ne zaman somutlaştırıldığını görmenizi sağlayan cppinsights.io web sitesini keşfettim . Bu kullanma hizmeti Ne oluyor ne düşündüğünü o çınlama ++ olduğunu gelmez aslında herhangi tanımlamak friend constexpr auto counter(slot<N>)her fonksiyonları writer<N, I>örneği.

counter(slot<N>)Zaten somutlaştırılmış olması gereken herhangi bir N'yi açıkça çağırmaya çalışmak, bu hipoteze temel teşkil ediyor gibi görünmektedir.

Ancak, writer<N, I>herhangi bir verilen için açıkça örneklemeye çalışırsam Nve Ibu zaten somutlaştırılmış olmalıysa, clang ++ yeniden tanımlanmış bir şeyden şikayet eder friend constexpr auto counter(slot<N>).

Yukarıdakileri test etmek için, önceki kaynak koduna iki satır daha ekledim.

int test1 = counter(slot<11>());
int test2 = writer<11,0>::value;

Her şeyi kendiniz için godbolt'da görebilirsiniz . Aşağıdaki ekran görüntüsü.

clang ++, tanımlanmadığına inandığı bir şeyi tanımladığına inanıyor

Yani, clang ++, tanımlanmadığına inandığı bir şeyi tanımladığına inanıyor gibi görünüyor, başınızı döndürüyor, değil mi?


İkinci soru grubu

  1. Geçici çözümüm hiç C ++ yasal mı yoksa başka bir g ++ hatası keşfetmeyi başardım mi?
  2. Yasal ise, bu nedenle bazı kötü clang ++ hataları keşfetti mi?
  3. Yoksa Tanımsız Davranış'ın karanlık yeraltı dünyasını araştırdım mı, bu yüzden suçlanacak tek kişi ben miyim?

Her halükarda, bu tavşan deliğinden çıkmama yardım etmek isteyen herkesi memnuniyetle karşılarım, gerekirse baş ağrısı açıklamalarını dağıtırım. : D



2
Standart komiteyi hatırladığım gibi, insanlar (varsayımsal olarak) her değerlendirildiklerinde tam olarak aynı sonucu vermeyen herhangi bir tür, şekil veya formda derleme zamanı yapılarına izin verme niyetinde. Bu yüzden bir derleyici hatası olabilir, "kötü biçimlendirilmiş, teşhis gerektirmez" bir vaka olabilir veya standardın kaçırdığı bir şey olabilir. Yine de "standardın ruhuna" aykırıdır. Üzgünüm. Zaman sayaçlarını da derlemek isterdim.
bolov

@HolyBlackCat İtiraf etmeliyim ki kafamı bu kodun etrafında bulmak çok zor. next()Fonksiyona parametre olarak monoton olarak artan bir sayıyı açıkça geçme ihtiyacını önleyebileceği gibi görünüyor , ancak bunun nasıl çalıştığını gerçekten anlayamıyorum. Her durumda, burada kendi sorunuma bir cevap buldum: stackoverflow.com/a/60096865/566849
Fabio A.

@FabioA. Ben de bu cevabı tamamen anlamıyorum. Bu soruyu sorduğumdan beri, bir kez daha constexpr sayaçlarına dokunmak istemediğimi fark ettim.
HolyBlackCat

Bu eğlenceli küçük bir düşünce deneyi olsa da, aslında bu kodu kullanan biri, C ++ 'ın gelecekteki sürümlerinde çalışmayacağını beklemek zorunda kalacak, değil mi? Bu anlamda sonuç kendini bir böcek olarak tanımlar.
Aziuth

Yanıtlar:


5

Daha fazla araştırmadan sonra, next()fonksiyonun gerçekleştirilebileceği küçük bir değişiklik olduğu ortaya çıkıyor , bu da kodun 7.0.0'ın üzerindeki clang ++ sürümlerinde düzgün çalışmasını sağlıyor, ancak diğer tüm clang ++ sürümleri için çalışmayı durduruyor.

Önceki kodumdan alınan aşağıdaki koda bir göz atın.

template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N>())+1>::value) {
    return R;
}

Buna dikkat ederseniz, kelimenin tam anlamıyla yaptığı şey, ilişkili değeri okumaya çalışmak slot<N>, ona 1 eklemek ve daha sonra bu yeni değeri aynı değerle ilişkilendirmektir slot<N>.

Ne zaman slot<N>hiçbir değeri ilişkilendirdiği, değer ile ilişkili slot<Y>olan, yerine alınır Yaz yüksek indeksi olan Nböyle slot<Y>bir ilişkili bir değeri vardır.

Yukarıdaki kod ile sorun, g ++ üzerinde çalışmasına rağmen, clang ++ (haklı olarak söyleyebilirim?) Hiçbir ilişkili değeri olmadığında döndürülen ne olursa olsun reader(0, slot<N>()) kalıcı olarak döndürür olmasıdır slot<N>. Buna karşılık, tüm yuvalar temel değerle etkili bir şekilde ilişkilendirilir 0.

Çözüm, yukarıdaki kodu buna dönüştürmektir:

template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N-1>())+1>::value) {
    return R;
}

Olarak slot<N>()değiştirildiğine dikkat edin slot<N-1>(). Bu bir anlam ifade ediyor: Bir değeri ilişkilendirmek istersem slot<N>, henüz bir değer ilişkilendirilmemiş demektir, bu nedenle onu geri almaya çalışmak mantıklı değildir. Ayrıca, bir sayacı artırmak istiyoruz ve bununla ilişkili sayacın slot<N>değeri bir artı artı ilişkili değer olmalıdır slot<N-1>.

Eureka!

Ancak clang ++ sürümleri <= 7.0.0'ı kırar.

Sonuçlar

Bana öyle geliyor ki, gönderdiğim orijinal çözümün kavramsal bir hatası var, öyle ki:

  • g ++ benim çözüm hata ile iptal ve sonuçta kod çalışması yine de yapar tuhaf / hata / rahatlama vardır.
  • clang ++ sürümleri> 7.0.0 daha katıdır ve orijinal koddaki hatayı sevmez.
  • clang ++ sürümleri <= 7.0.0, düzeltilmiş çözümü çalışmaz hale getiren bir hata var.

Tüm bunları özetlemek gerekirse, aşağıdaki kod g ++ ve clang ++ 'ın tüm sürümlerinde çalışır.

#if !defined(__clang_major__) || __clang_major__ > 7
template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N-1>())+1>::value) {
    return R;
}
#else
template <int N>
constexpr int next(int R = writer<N, reader(0, slot<N>())+1>::value) {
    return R;
}
#endif

As-is kodu msvc ile de çalışır. İcc derleyici SFINAE'yi tetiklemez decltype(counter(slot<N>())), deduce the return type of function "counter(slot<N>)"çünkü yapamamaktan şikayetçi olmayı tercih eder it has not been defined. Bunun bir hata olduğuna inanıyorum , bunun doğrudan sonucu üzerinde SFINAE yaparak çözülebilir counter(slot<N>). Bu, diğer tüm derleyiciler üzerinde de çalışır, ancak g ++, kapatılamayan çok can sıkıcı uyarılar vermeye karar verir. Yani, bu durumda #ifdefda kurtarmaya gelebilir.

Geçirmez Godbolt üzerinde aşağıda screnshotted.

resim açıklamasını buraya girin


2
Sanırım bu yanıt konuyu kapatıyor, ama yine de analizimde haklı olup olmadığımı bilmek istiyorum, bu nedenle kendi cevabımı doğru olarak kabul etmeden önce bekleyeceğim, başka birinin geçip bana daha iyi bir ipucu vereceğini umuyorum veya bir onay. :)
Fabio A.
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.