New things are always better? Wednesday 8th October 2008
Find the first non-digit in a string…
size_t FirstNonDigit( const char* s ) { site_t i; for( i = 0; i < strlen( s ); ++i ) { if( !isdigit( s[i] ) ) break; } return i == strlen(s) ? (size_t)-1 : i; }
Excellent... but old fashioned.
We really want to use iterator style code and while we're at it, lets use the C++ locale facilities and not the old fashioned C isxxx functions.
const char* FirstNonDigit( const char* s, const char* e ) { const std::locale& classic = std::locale::classic(); for( ; s != e; ++s ) { if( !std::isdigit( *s, classic ) ) break; } return s; }
Much more modern!
But wait, a manually rolled for loop? Surely this is a job for std::find_if.
namespace { struct IsNonDigit { bool operator()(char c) { return !std::isdigit( c, std::locale::classic() ); } }; } const char* FirstNonDigit( const char* s, const char* e ) { return std::find_if( s, e, IsNonDigit() ); }
Excellent, we managed to make that function body really simple... but we seemed to have sprouted an extra class.
We're not doing anything that exotic, perhaps we can just used some standard functions?
// Does not work const char* FirstNonDigit( const char* s, const char* e ) { return std::find_if( s, e, std::not1( std::bind2nd( std::isdigit< char >, std::locale::classic() ) ) ); }
Unfortunately, this doesn't work as std::bind2nd ends up trying to create a refence to a reference.
Luckily tr1 can come to our rescue... the bind here is that tr1::bind returns a function object that isn't derived from std::unary_function, so we can't pass it straight to std::not1, we have to bind it to std::logical_not.
const char* FirstNonDigit( const char* s, const char* e ) { return std::find_if( s, e, std::tr1::bind( std::logical_not< bool >(), std::tr1::bind( std::isdigit< char >, std::tr1::placeholders::_1, std::locale::classic() ) ) ); }
Excellent, it's a one liner!
Ouch! But seriously, which implementation do you prefer, and why?
Hmmm… good question. I think I’d go with the second version. I like the begin/end iterator style idiom but I find that once ‘bind’ starts getting involved some clarity is lost.
One big minus of the first implementation is that the result might depend on whether setlocale has been called earlier in the program and if so, it might also depend on the user and system environment.
I’m with you on this one. I’d prefer a more functional style but sometimes it’s just too clumsy in C++, and sometimes I can’t figure out the right combination of standard functors and adaptors.
One tweak I would use here is to avoid breaking out of the loop.
while (s != e && std::isdigit( *s, classic ) )
{
++s;
}