Commitment to a bit

Feb 08, 2009 17:47

So, my new year's resolution to myself was "more reuse". When solving problems, I want to consciously try to leverage exiting work, and refactor less (or at least less thoroughly). I hit my first snag last week.

One thing I've been trying to leverage is the C++ boost libraries. I had a need to convert base64 data back to binary. The boost dataflow iterators looked like a perfect fit. That's when the nightmare began.



It seemed like pretty simple. The documentation gives an example for encoding to base64. I could just use the inverse iterators in the same way, like this:

string foo(const string& a)
{
typedef transform_width<
binary_from_base64<
remove_whitespace>, 8, 6> base64_to_binary;

return std::string(
base64_to_binary(a.begin()),
base64_to_binary(a.end()));
}

No. I was using STLPort which defines a string iterator as const char*. The
transform_width and binary_from_base64 iterators are fine with this because they use the boost iterator traits facility. However, remove_whitespace doesn't and thus chokes on the raw pointer when it tries to get a ::value_type for the iterator to instantiate some helper bits. Moreover, remove_whitespace is built on a custom (read: unrelated) version of boost::filter_iterator which is appears to be broken because it only advances past filtered values on dereference which means the standard while( _first != _last ) *_first+=; loop does one iteration too many when there's whitespace at the end of a buffer.

I think I understand why that was done. The real boost::filter_iterator takes two parameters for it's constructor, whereas the dataflow iterator library only provides one. That second parameter is the "end" iterator that tells the filtering code where to stop. I'm not sure how code that used remove_whitespace was supposed to handle this, but suffice it to say I decided to roll my own version.

But what about my resolution? Here's what I came up with:

struct remove_whitespace_predicate
{
bool operator()(const char t) { return !std::isspace(t); }
};

typedef boost::filter_iterator<remove_whitespace_predicate, const char*>
remove_whitespace_super_t;

class remove_whitespace : public remove_whitespace_super_t
{
public:
struct start_end
{
start_end(const char* start, const char* end) :
start(start), end(end) { }
const char *start, *end;
};

remove_whitespace(const start_end& s) :
remove_whitespace_super_t(s.start, s.end) { }
};

The start_end is a way to pass multiple parameters through the single parameter templated constructors that the dataflow iterator library provides. This lets me write:

return std::string(
base64_to_binary(remove_whitespace::start_end(a.begin(), a.end())),
base64_to_binary(remove_whitespace::start_end(a.end(), a.end())));

Which is pretty good. A couple lines of code (and several of C++ boilerplate) to leverage boost and work around a small issue. If you ignore all the time it too to figure this all out, that was pretty productive...

c++, boost

Previous post Next post
Up