3 ms·
It looks like clang creates it's data structure representing the template instantiation, caches the instantiation for the parameterizing types, then begins inst
by knome 13y ago
It looks like clang creates it's data structure representing the template instantiation, caches the instantiation for the parameterizing types, then begins instantiating the types for the members. When it first hits the static bool, it reserves space for it's static value, then recurses into the static bools type in order to determine the value. Since the recursion uses the same parameterizing types, it finds the type it was midway through constructing, finds the space-reserved-but-no-appropriate-value-set static member in it, takes a 0 from that memory ( presumably from having allocated and zeroed the memory when initially saving space for the final value ).
For the MutualRec it may not set the static const value as being derived from a constexpr until after having retrieved the value, causing it to access the member in the cache before it's constexpr'ness has been determined.
For the Contra example, it would return 1 from having not'ed the 0 it found in the uninitialized static const.
Russel's paradox would be subverted in the same manner as the Contra example, returning true because it found false ( 0 ) in an uninitialized member variable after looking up the partially instatiated template instantiation in a cache.
This is all conjecture, but it fits, and it's where I would look first if debugging this. I do not know whether this is allowed by the standard, but I'd wager they did not account for referring cyclically to partially instantiated template parameterizations in a cache. GCC's errorful death is probably the correct action here, rather than clang's return of spurious values.
- pbsd 13y agoYou 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.