Monday, May 09, 2005

gcc __deprecation__ attributes

Long time no post. I've been busy putting together a 3.0 release of Gamera

The primary reason for a major version bump is to deprecate some functions that have long since bothered me: Many of Gamera's functions take arguments of the form (y, x). Not only is this self-inconsistent within Gamera, but it's against the long-established industry standard. I won't really get into how we got into that mess.

Unfortunately, you can't just deprecate "function(y, x)" and add "function(x, y)", because the compiler/interpreter can't tell the difference. Leaping from one to the other overnight would aggravate end users, and undoubtedly there'd be some lingering unconverted calls that would come back to haunt you. Adding new function names (such as "function_xy") might have worked, but the namespace is already really large and that would just kill autocompletion.

So the solution I ended upon was to use function overloading by type. "function(y, x)", and any version taking two numbers is deprecated, and "function(Point(x, y))" was added as an alternative. On the Python side, you can even go one step further and accept a two-element sequence everywhere a Point is required and "function((x, y))" works. Sure, it's a bit more typing, but on the other hand:

  • It's a much more graceful way to keep legacy code working (albeit with warnings)
  • You're more likely to catch places where you forgot to invert the coordinate pair
  • It encourages treating the logical coordinate pairs as a logical unit, which has a lot of advantages in terms of passing them around and manipulating them etc.


On the Python side, these changes were pretty straightforward, so I won't go into detail. Basically. I wrote some decorators to help "fake out" the function overloading, since function overloading is not a "built-in" feature of Python. I use the warn module to raise DeprecationWarnings at runtime when a deprecated call is made.

Now, onto the subject of the title: gcc has a nice little feature where you can set a deprecation attribute on a function or value and every time that function is called or the value is accessed, a warning is emitted by the compiler. Since this technique only works on gcc 3.1 or later, you probably want to put this in a macro that expands to nothing if the compiler doesn't support it. This is exactly the technique used in glib.


#if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))
#define GAMERA_CPP_DEPRECATED __attribute__((__deprecated__))
#else
#define GAMERA_CPP_DEPRECATED
#endif /* __GNUC__ */



Then you can write:


/*
ImageView<T>::get(size_t row, size_t col) is deprecated.

Reason: (x, y) coordinate consistency.

Use ImageView<T>::get(Point(col, row)) instead.
*/

GAMERA_CPP_DEPRECATED
value_type get(size_t row, size_t col) const {
return m_accessor(m_const_begin + (row * m_image_data->stride()) + col);
}


When you compile code (in test.cpp) that calls this function (in test.hpp) a warning is emitted:


test.cpp:10: warning `get' is deprecated (declared at test.hpp:0)


That's all fine and good, but this is C++, not C, so that statement is not entirely true. I've deprecated get(int, int), and replaced it with an overloaded version, get(Point). gcc doesn't display the entire function signature in the warning. If there is a way to convince it to do that, please let me know.

My solution to this was to write a filter for these warnings that would replace them with the comment in the source code directly above the deprecated declaration. So then, the feedback becomes:


ImageView<T>::get(size_t row, size_t col) is deprecated.

Reason: (x, y) coordinate consistency.

Use ImageView<T>::get(Point(col, row)) instead.


which is a whole lot more helpful -- though it requires some care to keep the comments in order.

Great, so 99 deprecated functions later, I'm all done, right? Well, no. On gcc 3.4 (which is on my primary development machine), you can put function attributes on C++ constructors right before the declaration, just the same as member functions and free functions. However, when I pushed the code out to OS-X, and MS-Windows, both having gcc 3.3, everything mysteriously broke. It seems that those earlier gcc versions, attributes need to go *after* the class constructors, but before just about everthing else. This unfortunately means you also can't define the constructor inline in the class, which is a common thing to do in heavily-templatized C++ code. Not impassable, but a minor pain, which certainly explains why the gcc folk fixed it.