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.)

13 comments ↓

#1 Max Lybbert on 12.10.12 at 11:04 am

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.

#2 Vasilis Vasaitis on 12.10.12 at 12:19 pm

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).

#3 Vasilis Vasaitis on 12.10.12 at 12:21 pm

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…)

#4 Z.T. on 12.10.12 at 12:38 pm

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.

#5 yossi kreinin on 12.10.12 at 1:40 pm

Is 4.7 known to be much better than 4.5? I'm open to experimenting.

#6 A on 12.13.12 at 2:09 pm

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);}

#7 David on 12.14.12 at 9:31 am

can you put post here the definition of Fixed, as well as what promote does to it ?

#8 Yossi Kreinin on 12.15.12 at 1:47 am

@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.

#9 Thomas Jürges on 12.19.12 at 2:31 pm

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. ;-)

#10 Yossi Kreinin on 12.21.12 at 11:57 pm

@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…

#11 Zhou Feng on 12.28.12 at 1:27 am

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.

#12 EschewPanache on 10.16.13 at 12:54 pm

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.

#13 mathrick on 01.27.14 at 4:39 am

@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"?

Leave a Comment