When compiling C++ projects (e.g. Rktcr), I use different compilers on different platforms:
Microsoft's cl.exe
from Visual Studio Express Edition on Windows,
Apple's clang++
from XCode on OSX,
and g++
from the GCC on Linux.
(These also use different versions of the C++ Standard Library, for added variability.)
This is great for catching errors in my code, as I get three different sets of static checks and three sets of design decisions (for those memory bugs that "just happen to work" on one system or another). But it also means I get exposed to a fair amount of compiler (and system library) quirkiness.
Today, I'm going to talk about one of the odder bits of behavior I've seen out of cl.exe
(Version 17.00.60315.1 for x86 from Visual Studio 2012 Update 2).
I ran across this a few months back, but have been lax in posting because I didn't have any sort of CSS styling set up for posting code in news.tchow.com ; a situation which I have partially rectified (no syntax highlighting or fancy line numbers; sorry).
The Code
Apologies about the spacing in this code. Blogger apparently converts tabs to single spaces. For reference, using spaces to indent code is wrong.
#include <iostream>
template< int N >
struct TrivialTemplate {
int val;
typedef TrivialTemplate< N > type;
};
struct NoTemplate {
int val;
};
//Consider different types of Coord:
#if COORD==1
typedef NoTemplate Coord; //<-- (1)
#elif COORD==2
typedef TrivialTemplate< 27 > Coord; //<-- (2)
#elif COORD==3
typedef TrivialTemplate< 27 >::type Coord; //<-- (3)
#endif
struct Pt {
union {
Coord arr[2];
struct {
Coord x;
Coord y;
};
};
};
int main(int, char **) {
Pt pt;
pt.x.val = 12;
std::cout << pt.arr[0].val << std::endl;
return 0;
}
This C++ code defines type Pt
, which makes use of a struct wrapped in a union to allow individual coordinate access as pt.x
and pt.y
, and easy coordinate indexing with pt.arr[i]
.
(This method can be extended to allow synonyms like pt.x
and pt.r
and subvectors like pt.xy
or pt.yz
.
I use it in my Vector.hpp.)
The Problem
Notice that the lines marked (1)
, (2)
, and (3)
all define Coord
to be the same type -- a struct
with a single int
as a member -- but in slightly different ways.
So, with the line marked (1)
, things compile fine:
c:\Users\ix\Code\rktcr>cl /nologo /EHsc /W3 /WX /DCOORD=1 weird_template.cpp weird_template.cpp c:\Users\ix\Code\rktcr>weird_template.exe 12 c:\Users\ix\Code\rktcr>
If I use line (2)
, I get some weirdness:
c:\Users\ix\Code\rktcr>cl /nologo /EHsc /W3 /WX /DCOORD=2 weird_template.cpp weird_template.cpp weird_template.cpp(20) : error C2621: member 'Pt::arr' of union 'Pt::<unnamed-tag>' has copy constructor weird_template.cpp(31) : error C2039: 'arr' : is not a member of 'Pt' weird_template.cpp(18) : see declaration of 'Pt' weird_template.cpp(31) : error C2228: left of '.val' must have class/struct/union
So, what happened?
Well, cl.exe
has (presumably) synthesized a copy constructor for Pt::arr
-- which, after some typedef
expansion -- is an array of TrivialTemplate< 27 >
.
Now, having non-trivial constructors and destructors for data in a union is problematic, because the whole point of a union
is that its members overlap in memory.
However, it's also odd that a copy constructor would be synthesized -- after all, TrivialTemplate< 27 >
is functionally identical to NoTemplate
, and no constructor was synthesized there.
Indeed, neither g++
or clang++
have any problems compiling this code.
But here's where it gets really weird.
If I compile with the line marked (3)
active -- that is, if we talk about TrivialTemplate< 27 >
using a typedef
it contains for itself,
one would expect the same error.
Instead, it just works fine:
c:\Users\ix\Code\rktcr>cl /nologo /EHsc /W3 /WX /DCOORD=3 weird_template.cpp weird_template.cpp c:\Users\ix\Code\rktcr>weird_template.exe 12 c:\Users\ix\Code\rktcr>
So, where does that leave me?
Spending time writing workarounds -- in this case, my planar map code (which uses templated classes wrapping integers to make sure it isn't going to overflow) now has a fair number of uses of the somewhat odd-looking BitsInt< N >::self
.
This is certainly a lot nicer than the llvm-gcc
bug that incorrectly passed unions to functions (effectively reordering function parameters).
That bug led to me abandoning Apple-supplied compilers on OSX until clang
was mature enough.
No comments:
Post a Comment