C++ template fuckwittery
You're kidding, right?
(gdb) bt
#0 0xaf88f2e0 in std::lround<Fixed<long, 14> > (__x=51.35198974609375) at /usr/include/c++/4.5/tr1_impl/cmath:710
#1 0xaf88f2e8 in std::lround<Fixed<long, 14> > (__x=51.35198974609375) at /usr/include/c++/4.5/tr1_impl/cmath:710
#2 0xaf88f2e8 in std::lround<Fixed<long, 14> > (__x=51.35198974609375) at /usr/include/c++/4.5/tr1_impl/cmath:710
#3 0xaf88f2e8 in std::lround<Fixed<long, 14> > (__x=51.35198974609375) at /usr/include/c++/4.5/tr1_impl/cmath:710
#4 0xaf88f2e8 in std::lround<Fixed<long, 14> > (__x=51.35198974609375) at /usr/include/c++/4.5/tr1_impl/cmath:710
#5 0xaf88f2e8 in std::lround<Fixed<long, 14> > (__x=51.35198974609375) at /usr/include/c++/4.5/tr1_impl/cmath:710
This goes on for a few tens of thousands of stack frames. Time to open /usr/include/c++/4.5/tr1_impl/cmath:710, which has
this little gem:
template<typename _Tp>
inline long
lround(_Tp __x)
{
typedef typename __gnu_cxx::__promote<_Tp>::__type __type;
return lround(__type(__x));
}
What happened? Fucked if I know (it's a bit hard to get to the bottom of the problem without being able to get to the bottom
of the call stack, for starters; ought to figure out a better way than hitting Enter screen after screen, with gdb asking if I
really want to proceed).
Did someone define an std::lround? (A quick grep didn't show signs of that fuckwittery; though I found a couple of
std::mins and std::maxes, leading to colorful consequences.)
Did someone define a template lround and did a using namespace std?
Did someone define an implicit casting operator that used to be called here, before this lround template appeared, and became
a better match for the argument, whatever that means?
Fucked if I know.
I'm sure this lround business wasn't ever supposed to call itself, though it rather obviously can, depending on what the
__gnu_cxx::__promote<_Tp>::__type does.
All that from trying to upgrade from g++ 4.2 to g++ 4.5 (and to gnu++0x – a C++0x flavor brought to you by GNU, enriched by
GNU extensions such as strdup.) Oh the joys and safety of static binding – statically changing the meaning of your code with
every compiler upgrade!
It's a good thing I rarely get to deal with C++ these days.
(Why upgrade to C++0x, a.k.a. C++11, a.k.a C++0xb? Lambdas, for one thing. Also future job interviews. Embrace C++11, or die
trying.)
For what it's worth, it looks like the typedef uses GCC-specific
machinery to do some kind of cast (my guess is to remove const and
volatile, but that's only a guess). The last line looks like a recursive
call, but I suspect it's meant to call an overloaded function that uses
takes the promoted type chosen by the typedef.
My diagnosis is that the second function doesn't exist for long, so
the thing gets stuck in a recursive loop. However, I can't figure out
what value there is in rounding longs, they're integral types so they
have no fractional part to round.
I suspect that this is a bug in GCC's library. I have a hard time
believing that you'd call lround directly. Instead, I'm sure you called
another function that erroneously calls lround.
I don't have GCC 4.5, but what seems to be happening here is the
following:
- libstdc++ has a few (>= 1) non-template lround() functions which
handle some specific types.
- libstdc++ also has the generic, templated version shown above, which
tries to promote the type it is given to one that's eventually handled
by the non-template versions.
The diagnosis here would be to check what __promote does for your
type; but it already looks like it's essentially the identity function.
Which is the weird part; it should be doing something sensible or
failing to compile.
In fact (so I got curious and starting digging around the web for the
libstdc++4.5 header files), if you look at , that's exactly what's
happening: __promote merely serves as identity, at least for types that
__is_integer is not defined. This seems to be a bug, and it's been fixed
in 4.7, where the generic version of __promote does not define __type at
all. (And also in 4.7 the templated lround() simply calls
__builtin_lround() and doesn't bother with any of this promoting
chicanery).
Last paragraph should read: "[...] if you look at ext/type_traits.h,
[...]" (looks like your blog code strips anything in angle brackets,
which is probably a good idea, but still...)
Did you try GCC 4.7? Did you try clang 3.2? Even if they can't
generate assembly for your arch, they might be useful as static
analyzers.
Is 4.7 known to be much better than 4.5? I'm open to
experimenting.
I bet you already know the "explanation" for what's actually
happening at the compiler/library level here, but for the benefit of
*anyone* who feels confused, here's my amateur take on it:
The standard library defines lround(float), lround(double), and
lround(long double). It also defines a template function lround(T), as
pasted in your post, whose job is to promote the T parameter to a new,
more appropriate type __type and then call lround(__type) on the
promoted value.
How is this new type "__type" determined? Well, if the original type
was an integral type, the new type is "double". Otherwise, __promote is
a no-op. Here's the code:
http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a02061.html#l00161
00164 template<typename _Tp, bool =
std::__is_integer::__value>
00165 struct __promote
00166 { typedef double __type; };
00167
00168 template
00169 struct __promote
00170 { typedef _Tp __type; };
Now, in Yossi's code, he's calling lround() on a value of type
Fixed<long,14>. Presumably the Fixed type has an overloaded
conversion "operator double()" or the like, and the original programmer
expected that lround(fixedval) would be evaluated as
lround(double(fixedval)). Unfortunately, GNU's library isn't playing
along.
One workaround would be to insert the explicit cast everywhere
lround() is called. Another (better?) workaround would be to provide
your own overload or specialization of lround(), and make sure it's
visible in all files where lround is called. Yet another (much much
worse!) workaround would be to specialize std::__is_integer for your
Fixed type.
In libc++, this library bug has been fixed by writing clearer code in
the first place: std::lround<T> is enabled only for integral
types, using the clever (TOO clever, I'm sure Yossi will say)
"enable_if" construct.
template
inline _LIBCPP_INLINE_VISIBILITY
typename enable_if<is_integral::value, long>::type
lround(_A1 __x) {return lround((double)__x);}
can you put post here the definition of Fixed, as well as what
promote does to it ?
@A: you have rather thorough knowledge of this shit. I remember when
I used to have rather thorough knowledge of this shit myself. I sure
hope the process of interacting with the various new versions of gcc and
their C++11 support will not force me to thoroughly learn this shit
again. enable_if... fuck_me.
@David: no, that would be too embarrassing. The upshot is that it has
an operator float and an operator double, I think – implicit casts
because C++98 doesn't have an explicit cast, only explicit constructors,
and now it's a bit too late to change the thousands of casts in user
code to explicit ones. As to what promote does to it – I think that the
recursion above demonstrates that promote does nothing to it, or rather
it's an identity function, if you can call this shit a function.
Unpaged output in gdb?
Enter "set pagination off" in gdb or add it to ${HOME}/.gdbinit for
automatic execution. Then welcome the gazillions of identical lines of
back trace. ;-)
@Thomas: aha! set pagination off. Maybe it's worth adding to the
company-wide .gdbinit... I wonder what happens under TUI and to what
extent cmd.exe can scroll through the output though...
C++ is a cuntful language. When it is nice and dripping there is no
better place to be, but when those mucus membranes dry up, it will tear
the skin off your dick.
I generally avoid the use of templates at all costs. I find them
objectionable.
I am still laughing! I have been calling myself a programmer for 30+
years (reference to another blog) and can still learn things right
here!
Particularly that last bit about avoiding templates!
Thanks, folks!
carl.
@A: “How is this new type "__type" determined? Well, if the original
type was an integral type, the new type is "double".”
Assuming that's accurate, who the hell rounds long to long by
converting it to double!? Is that one of those mythical "smart
compilers" which make templates "as performant as hand-written
code"?
Post a comment