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.
#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.
~/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.
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.
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.
Edit: checked iso/iec 14882 (c++03), 14.7.1 Implicit instantiation. §14 "The result of an infinite recursion in instantiation is undefined".
UPDATE: Forget the part about conformance - if it is undefined behavior every result is of course correct. I made the wrong assumption that the result should definitely be an error if the correct behavior is not defined or can not be attained.
Nevertheless, I think it's bad form for a compiler to take full advantage of undefined behaviour. Reinterpreting undefined behaviour as “should not compile” (failing that, “must crash”) is possible for almost all of C99; CompCert (formally verified C compiler) obviously does it, and the implementation is the easy part (formal verification being the complicated part).
That's not a lack of soundness. It's a lack of specificity. For cases like this, that's actually a feature, not a bug.
> Nevertheless, I think it's bad form for a compiler to take full advantage of undefined behaviour.
What exactly is "take full advantage of undefined behaviour"? Isn't any behaviour just as different from "undefined" as anything else?
> Reinterpreting undefined behaviour as “should not compile” (failing that, “must crash”) is possible for almost all of C99; CompCert (formally verified C compiler) obviously does it, and the implementation is the easy part (formal verification being the complicated part).
Clang not only has an ability to do this, it includes a flag to do a quick check specifically for undefined behaviour as well as an ability to provide a trap for undefined behaviour, which is terribly useful for tool builders.