TL; DR
Bu yazının tamamını okumaya çalışmadan önce şunu bilin:
- sunulan konuya bir çözüm buldum , ancak yine de analizin doğru olup olmadığını bilmek için sabırsızlanıyorum;
- Çözümü
fameta::counter
kalan birkaç tuhaflığı çözen bir sınıfa paketledim . Sen edebilirsiniz github bulmak ; - 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
- Bu tanıma gerçekten katılıyor musunuz?
- Evet ise, bu yeni davranış standart tarafından zorunlu tutulur mu? Birincisi hata mıydı?
- 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 .
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 N
ve I
bu 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ü.
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
- Geçici çözümüm hiç C ++ yasal mı yoksa başka bir g ++ hatası keşfetmeyi başardım mi?
- Yasal ise, bu nedenle bazı kötü clang ++ hataları keşfetti mi?
- 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
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