4 ms·
You are correct. The Contra example is not different from the much simpler program #include <iostream> int main() { static bool p = !p;
by pbsd 13y ago
You are correct. The Contra example is not different from the much simpler program
#include <iostream>
int main()
{
static bool p = !p;
std::cout << p << std::endl;
}
The static storage specification ensures that p is initialized to 0 before main is executed, and the standard's scoping rules (§3.3.2) allows a variable to reference itself right after declaration.
The same thing happens with Russell's Paradox, with the added twist that HasElement<M, M> always resolves to the specialization HasElement<M, T> (it 'fits' better) rather than the general version, which is never used.
- knome 13y ago~/test/O$ cat break.cpp #include <iostream> template <typename A, typename B> struct MutualRecursion { static const int a = MutualRecursion< B, A >::b ; static const int b = MutualRecursion< B, A >::a ; }; int main( int argc, char ** argv ){ std::cout << MutualRecursion< float, int >::b << std::endl; return 0; }; ~/test/O$ clang break.cpp break.cpp:6:49: error: no member named 'b' in 'MutualRecursion<float, int>' static const int a = MutualRecursion< B, A >::b ; ~~~~~~~~~~~~~~~~~~~~~~~~~^ break.cpp:6:24: note: in instantiation of template class 'MutualRecursion<int, float>' requested here static const int a = MutualRecursion< B, A >::b ; ^ break.cpp:11:16: note: in instantiation of template class 'MutualRecursion<float, int>' requested here std::cout << MutualRecursion< float, int >::b << std::endl; ^ break.cpp:7:24: error: in-class initializer is not a constant expression static const int b = MutualRecursion< B, A >::a ; ^~~~~~~~~~~~~~~~~~~~~~~~~~ 2 errors generated. ~/test/O$ ~~This is definitely a bug in clang~~. clang doesn't find the member "b" here because it's referencing the cached still-in-construction version of the template. Furthermore, it still does this even when "b" is a regular constant. // static const int b = MutualRecursion< B, A >::a ; static const int b = 10 ; gcc, since it appears to always instantiate a new copy of the template compiles successfully for when "b = 10". ~~clang needs to wait until it has fully constructed a type before putting it into the cache, even if it might be slower in some recursive cases. Better than wrong in them.~~ I don't know if clang is in the wrong here, so I'm rejecting a couple of statements. Its method does not allow for compilation in these circumstances, but may be technically permissible, even if gcc jives with how I envision templates operating.
- pbsd 13y agoI don't see how that could not be a compiler error. The case b = 10 is more interesting because, as far as I can tell, it shows the differences between compilers that do two-phase lookup correctly and those that don't. This seems to be a rare case where GCC is in the right, but I'd have to double-check the standard to be sure.
- knome 13y agoIt was pointed out to me after I joined #llvm when considering reporting this that using the value of "b", which is defined after "a" in the class before it has been initialized is, I suppose obviously in retrospect, undefined behavior. In effect, I was subverting the undefined behavior associated with expanding a set of infinitely deep template instantiations by accessing a variable that was as yet undefined at the point of access. So, my initial retractions stand, and this compiler behavior will remain appropriately undefined.