How to abuse a C++ compiler?(mysticalprogramming.wordpress.com)
mysticalprogramming.wordpress.com
How to abuse a C++ compiler?
http://mysticalprogramming.wordpress.com/2014/03/19/how-to-abuse-a-cpp-compiler/
39 comments
I tried to make a proposal for a more natural meta programming:
https://github.com/hun-nemethpeter/cpp-reflector-mini/blob/m...
https://github.com/hun-nemethpeter/cpp-reflector-mini/blob/m...
D has very good support for this. here's a quick string templating function i wrote, for instance: https://gist.github.com/martindemello/2686640
it calls a function at compile time to expand placeholders, and then compiles the generated string via the mixin() statement and carries on with the code. far more pleasant than c++.
it calls a function at compile time to expand placeholders, and then compiles the generated string via the mixin() statement and carries on with the code. far more pleasant than c++.
constexpr doesn't really help for logic programming though, which is what this post is using template specialization to do. You can't ask for the answer to Russell's paradox using constexpr (which is, IMHO, a good thing).
> Type-level meta programming is still confined to using templates though...
Which there is less of need for this, since we have better type deduction in C++11 and 14 as well.
Which there is less of need for this, since we have better type deduction in C++11 and 14 as well.
I think it is neck and neck whether C++ template instantiation or XSLT is the ugliest unintentional functional programming language.
I'd say C++ templates are a clear winner in this contest. Simple due to the sheer volume of unintelligible nonsense the compiler produces if there is an error in the template.
Clang would beg to differ.
I'm pretty sure XSLT functional paradigm was intentional.
XSLT 2 functional programming was intentional, in XSLT 1 it was more accidentally discovered http://fxsl.sourceforge.net/articles/FuncProg/Functional%20P...
It's funny that in C++ this is 'abuse of the compiler' and it is awkward, in lisp this is called 'writing lisp code'.
That's because it is awkward.
One of the fundamental properties people expect from a step called "compilation" is that it finishes. Also templates notation wasn't designed for this kind of use, and does not scale well for complex programs.
In Lisp code generation is done in Lisp, a notation that scales quite well, and there isn't a separarted step called "compilation". There is no general assumption that "execution" finishes.
One of the fundamental properties people expect from a step called "compilation" is that it finishes. Also templates notation wasn't designed for this kind of use, and does not scale well for complex programs.
In Lisp code generation is done in Lisp, a notation that scales quite well, and there isn't a separarted step called "compilation". There is no general assumption that "execution" finishes.
> That's because it is awkward.
It's more than a bit awkward really.
Still, the OP is right: it's funny.
It's more than a bit awkward really.
Still, the OP is right: it's funny.
Well, if you wrote a Lisp macro that recursively called itself until the compiler crashed, you might call that 'abuse of the compiler', too...
... in order to evaluate InfRec<T>::value ...
I've been spoiled lately, this is jarring to read. InfiniteRecursion<T>::value
C'mon, spoil me some more, text is cheap :-)Horizontal space isn't.
I don't understand clang's behavior in the last three examples, but then again I'm not a C++ expert. Is it conforming, and is it reasonable?
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.
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.
You are correct. The Contra example is not different from the much simpler program
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.
#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.
I 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.
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.
It 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.
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.
As far as I know, infinite recursion during template instantiation is undefined behavior.
Edit: checked iso/iec 14882 (c++03), 14.7.1 Implicit instantiation. §14 "The result of an infinite recursion in instantiation is undefined".
Edit: checked iso/iec 14882 (c++03), 14.7.1 Implicit instantiation. §14 "The result of an infinite recursion in instantiation is undefined".
[deleted]
The answer yes is not reasonable for Russell's paradox and therefore most likely not conforming, too. (Assuming the program really encodes Russell's paradox which I did not try to verify.)
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.
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.
Clang is mathematically wrong in all but the first case.
I'm not sure what it says about C++ conformance; the problem is likely Clang but I'm not sure how sound template metaprogramming or C++ templates are supposed to be.
As others have noted, infinite template recursion is undefined. It could produce a program that does whatever it wants and still be conforming.
So the lack of soundness is in the C++ templates spec.
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).
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).
> So the lack of soundness is in the C++ templates spec.
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.
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.
So a compiler must emit a check for signed integer overflow on every integer operation? That seems absurd. It makes sense to optionally instrument for that (as clang offers) but is otherwise ridiculous.
Fingermann's assertion: at the point where a preprocessor becomes Turing complete it is time to write a preprocessor for it
C++'s template mechanism is not implemented by the preprocessor.
Templates control actual code generation (that's sort of their point, with generics) so they must be handled later in the compilation, by the compiler proper.
Templates control actual code generation (that's sort of their point, with generics) so they must be handled later in the compilation, by the compiler proper.
as far as I can tell, the Russell's paradox example would be the same without the template "template<typename U, typename V>"?
in other words, both compilers seem to be being consistent with the previous demonstrated behavior using self-recursion, and the more generic template is being ignored altogether.
Am I perhaps missing a reason it was included?
in other words, both compilers seem to be being consistent with the previous demonstrated behavior using self-recursion, and the more generic template is being ignored altogether.
Am I perhaps missing a reason it was included?
The first thing I thought was using C++ templates like Haskell.
Not that unusual, though there are some subtle differences in what C++ templates can do.
C++ is not Haskell, but with C++11/C++14 goodies, it does feel a bit closer to it.
you should find metafun interesting: http://gergo.erdi.hu/projects/metafun/
constexpr functions where quite restrictive in C++11. They could pretty much only contain a single expression and a return. C++14 significantly relaxes these restrictions.
http://en.wikipedia.org/wiki/C%2B%2B14#Relaxed_constexpr_res...
Type-level meta programming is still confined to using templates though...