Hello, I'd like to propose changes to the SAL_INFO etc. family of the new logging functions that would replace the somewhat strange usage
SAL_INFO("foo", "string " << s << " of length " << n) with SAL_INFO("foo", "string %1 of length %2", s, n ) while still leaving the possibility to do SAL_INFO("foo", "string " + s + " of length " + OUString::valueOf( n )) The last two are IMO much more natural than the iostream-based usage. The format-based usage uses a printf-like function that, unlike printf, is typesafe and extensible. If people would be interested, the function itself could be made public API (after all, there's a reason why printf is still popular even nowadays, despite all its shortcomings). Attached source code has a testing implementation of the format function and a simple logging macro. It still needs few final touches but it's generally ready. The templates may look scary at first, but it's rather simple, they are just making the function to take up to 9 arguments of each of the supported arguments and the template for return type is SFINAE[1]. I also had a look at the resulting code and it's usually pretty small (can be tweaked by where the inline keyword is put, this way the call at the place of usage is very small) and reasonably fast (could be done even faster if OUString being actually rather lame didn't make it somewhat pointless[2]). Now, onto the questions: 1) Yes/no ? 2) It take it SAL_INFO, being in sal, has to keep binary compatibility, which means we're stuck with the << usage if it stays that way in the 3.5 branch, and that I'd have to make this new way binary compatible in time for 3.5 if it's to replace it? 3) What would 'in time for 3.5' mean in practice? 4) What is the LO policy on char* <-> OUString conversions? It seems to me they always need to be explicit, but I'd prefer to ask. 5) For some of the features to work, it is necessary to build without gcc's -pedantic. I expect we already would generate some warnings if that was used anyway, wouldn't we? BTW, I've tried and built the code with GCC4.5 and MSVC 2008. [1] http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error [2] Today's trivia: Did you know that despite being seemingly highly optimized, with stuff like OUStringBuffer::makeStringAndClear() or the obnoxious RTL_CONSTASCII_USTRINGPARAM macro, O(U)String* actually does some rather pathetic things like ctors first checking if there's any memory to free or allocated memory being always zeroed first? -- Lubos Lunak l.lu...@suse.cz
// TODO all the OUStringBuffers appear to be rather inefficient, so either do something about them // or don't bother about the performance of this code that much #include <rtl/ustrbuf.hxx> #include <string.h> /** * Base class for formatting any type to a string. To add a new type, create a subclass * like in the following example. * @code template<> class Formatter< const sal_Char* > : public FormatterBase { public: Formatter( const sal_Char* arg ) : data( arg ) {} typedef const sal_Char* Type; virtual void append( rtl::OUStringBuffer& str ) const; private: const sal_Char* const data; }; void Formatter< const sal_Char* >::append( rtl::OUStringBuffer& str ) const { str.appendAscii( data ); } * @endcode */ class FormatterBase { public: virtual void append( rtl::OUStringBuffer& str ) const = 0; // TODO not really much point with OUStringBuffer being otherwise inefficient? virtual int length() const = 0; }; template< typename T > class Formatter {}; template<> class Formatter< const sal_Char* > : public FormatterBase { public: Formatter( const sal_Char* arg ) : data( arg ) {} typedef const sal_Char* Type; virtual void append( rtl::OUStringBuffer& str ) const; private: const sal_Char* const data; }; void Formatter< const sal_Char* >::append( rtl::OUStringBuffer& str ) const { str.appendAscii( data ); } template< int N > class Formatter< char[N] > : public Formatter< const sal_Char* > { public: Formatter( const sal_Char* arg ) : Formatter< const sal_Char* >( arg ) {} }; template< int N > class Formatter< const char[N] > : public Formatter< const sal_Char* > { public: Formatter( const sal_Char* arg ) : Formatter< const sal_Char* >( arg ) {} }; // for types that work with OUStringBuffer::append(), 'cast' is a cast // applied to the data when calling append() if needed #define FORMATTER_APPEND_BASED( type, cast ) \ template<> \ class Formatter< type > : public FormatterBase \ { \ public: \ Formatter( const type& arg ) : data( arg ) {} \ typedef type Type; \ virtual void append( rtl::OUStringBuffer& str ) const; \ private: \ const type& data; \ }; \ \ void Formatter< type >::append( rtl::OUStringBuffer& str ) const \ { \ str.append( cast data ); \ } FORMATTER_APPEND_BASED( rtl::OUString, ) // MSVC requires the cast otherwise it's ambiguous (@%$@!! sal_Int types) FORMATTER_APPEND_BASED( int, (sal_Int64) ) // there's no unsigned overload for OUStringBuffer::append(), so just cast it to sal_Int64 FORMATTER_APPEND_BASED( unsigned int, (sal_Int64) ) // there's no integer conversion when working with template arguments, so extra class for each int type is needed FORMATTER_APPEND_BASED( short int, (sal_Int64) ) FORMATTER_APPEND_BASED( unsigned short int, (sal_Int64) ) FORMATTER_APPEND_BASED( long int, ) FORMATTER_APPEND_BASED( unsigned long int, (sal_Int64) ) // TODO these possibly lose significant digits FORMATTER_APPEND_BASED( long long int, (sal_Int64) ) FORMATTER_APPEND_BASED( unsigned long long int, (sal_Int64) ) FORMATTER_APPEND_BASED( bool, (sal_Bool) ) //int Formatter< int >::length() const //{ // cannot be exact, give maximum // return 40; //} rtl::OUString formatInternal( const rtl::OUString& str, const FormatterBase* f[], int fcount ) { // rtl::OUStringBuffer buffer( str.getLength() + f1.length() + f2.length() + f3.length()); rtl::OUStringBuffer buffer; for( int pos = 0; pos < str.getLength(); ++pos ) { if( str[ pos ] == '%' && pos < str.getLength() - 1 ) { ++pos; // TODO warn if no argument for format if( str[ pos ] >= '1' && str[ pos ] <= '0' + fcount ) f[ str[ pos ] - '1' ]->append( buffer ); else buffer.append( str[ pos ] ); } else buffer.append( str[ pos ] ); } return buffer.makeStringAndClear(); } /** * The purpose of this template is only to ensure that format() is called only with arguments for which a matching Formatter * class exists, template instances with invalid arguments will not be created because of this class. The return type * of format() is actually rtl::OUString. */ template< typename T1, typename T2 = int, typename T3 = int, typename T4 = int, typename T5 = int, typename T6 = int, typename T7 = int, typename T8 = int, typename T9 = int > struct FormatterTypesCheck { typedef rtl::OUString Type; }; /** * Create a new string based on the given format string with placeholders filled in with the given arguments. * * This is a printf-like function that is typesafe and extensible. */ template< typename T > typename FormatterTypesCheck< typename Formatter< T >::Type >::Type format( const rtl::OUString& str, const T& arg ) { Formatter< T > f( arg ); const FormatterBase* fs[] = { &f }; return formatInternal( str, fs, SAL_N_ELEMENTS( fs )); } /** * @overload */ template< typename T1, typename T2 > typename FormatterTypesCheck< typename Formatter< T1 >::Type, typename Formatter< T2 >::Type >::Type format( const rtl::OUString& str, const T1& arg1, const T2& arg2 ) { Formatter< T1 > f1( arg1 ); Formatter< T2 > f2( arg2 ); const FormatterBase* fs[] = { &f1, &f2 }; return formatInternal( str, fs, SAL_N_ELEMENTS( fs )); } /** * @overload */ template< typename T1, typename T2, typename T3 > typename FormatterTypesCheck< typename Formatter< T1 >::Type, typename Formatter< T2 >::Type, typename Formatter< T3 >::Type >::Type format( const rtl::OUString& str, const T1& arg1, const T2& arg2, const T3& arg3 ) { Formatter< T1 > f1( arg1 ); Formatter< T2 > f2( arg2 ); Formatter< T3 > f3( arg3 ); const FormatterBase* fs[] = { &f1, &f2, &f3 }; return formatInternal( str, fs, SAL_N_ELEMENTS( fs )); } /** * @overload */ template< typename T1, typename T2, typename T3, typename T4 > typename FormatterTypesCheck< typename Formatter< T1 >::Type, typename Formatter< T2 >::Type, typename Formatter< T3 >::Type, typename Formatter< T4 >::Type >::Type format( const rtl::OUString& str, const T1& arg1, const T2& arg2, const T3& arg3, const T4& arg4 ) { Formatter< T1 > f1( arg1 ); Formatter< T2 > f2( arg2 ); Formatter< T3 > f3( arg3 ); Formatter< T4 > f4( arg4 ); const FormatterBase* fs[] = { &f1, &f2, &f3, &f4 }; return formatInternal( str, fs, SAL_N_ELEMENTS( fs )); } /** * @overload */ template< typename T1, typename T2, typename T3, typename T4, typename T5 > typename FormatterTypesCheck< typename Formatter< T1 >::Type, typename Formatter< T2 >::Type, typename Formatter< T3 >::Type, typename Formatter< T4 >::Type, typename Formatter< T5 >::Type >::Type format( const rtl::OUString& str, const T1& arg1, const T2& arg2, const T3& arg3, const T4& arg4, const T5& arg5 ) { Formatter< T1 > f1( arg1 ); Formatter< T2 > f2( arg2 ); Formatter< T3 > f3( arg3 ); Formatter< T4 > f4( arg4 ); Formatter< T5 > f5( arg5 ); const FormatterBase* fs[] = { &f1, &f2, &f3, &f4, &f5 }; return formatInternal( str, fs, SAL_N_ELEMENTS( fs )); } /** * @overload */ template< typename T1, typename T2, typename T3, typename T4, typename T5, typename T6 > typename FormatterTypesCheck< typename Formatter< T1 >::Type, typename Formatter< T2 >::Type, typename Formatter< T3 >::Type, typename Formatter< T4 >::Type, typename Formatter< T5 >::Type, typename Formatter< T6 >::Type >::Type format( const rtl::OUString& str, const T1& arg1, const T2& arg2, const T3& arg3, const T4& arg4, const T5& arg5, const T6& arg6 ) { Formatter< T1 > f1( arg1 ); Formatter< T2 > f2( arg2 ); Formatter< T3 > f3( arg3 ); Formatter< T4 > f4( arg4 ); Formatter< T5 > f5( arg5 ); Formatter< T6 > f6( arg6 ); const FormatterBase* fs[] = { &f1, &f2, &f3, &f4, &f5, &f6 }; return formatInternal( str, fs, SAL_N_ELEMENTS( fs )); } /** * @overload */ template< typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7 > typename FormatterTypesCheck< typename Formatter< T1 >::Type, typename Formatter< T2 >::Type, typename Formatter< T3 >::Type, typename Formatter< T4 >::Type, typename Formatter< T5 >::Type, typename Formatter< T6 >::Type, typename Formatter< T7 >::Type >::Type format( const rtl::OUString& str, const T1& arg1, const T2& arg2, const T3& arg3, const T4& arg4, const T5& arg5, const T6& arg6, const T7& arg7 ) { Formatter< T1 > f1( arg1 ); Formatter< T2 > f2( arg2 ); Formatter< T3 > f3( arg3 ); Formatter< T4 > f4( arg4 ); Formatter< T5 > f5( arg5 ); Formatter< T6 > f6( arg6 ); Formatter< T7 > f7( arg7 ); const FormatterBase* fs[] = { &f1, &f2, &f3, &f4, &f5, &f6, &f7 }; return formatInternal( str, fs, SAL_N_ELEMENTS( fs )); } /** * @overload */ template< typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8 > typename FormatterTypesCheck< typename Formatter< T1 >::Type, typename Formatter< T2 >::Type, typename Formatter< T3 >::Type, typename Formatter< T4 >::Type, typename Formatter< T5 >::Type, typename Formatter< T6 >::Type, typename Formatter< T7 >::Type, typename Formatter< T8 >::Type >::Type format( const rtl::OUString& str, const T1& arg1, const T2& arg2, const T3& arg3, const T4& arg4, const T5& arg5, const T6& arg6, const T7& arg7, const T8& arg8 ) { Formatter< T1 > f1( arg1 ); Formatter< T2 > f2( arg2 ); Formatter< T3 > f3( arg3 ); Formatter< T4 > f4( arg4 ); Formatter< T5 > f5( arg5 ); Formatter< T6 > f6( arg6 ); Formatter< T7 > f7( arg7 ); Formatter< T8 > f8( arg8 ); const FormatterBase* fs[] = { &f1, &f2, &f3, &f4, &f5, &f6, &f7, &f8 }; return formatInternal( str, fs, SAL_N_ELEMENTS( fs )); } /** * @overload */ template< typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9 > typename FormatterTypesCheck< typename Formatter< T1 >::Type, typename Formatter< T2 >::Type, typename Formatter< T3 >::Type, typename Formatter< T4 >::Type, typename Formatter< T5 >::Type, typename Formatter< T6 >::Type, typename Formatter< T7 >::Type, typename Formatter< T8 >::Type, typename Formatter< T9 >::Type >::Type format( const rtl::OUString& str, const T1& arg1, const T2& arg2, const T3& arg3, const T4& arg4, const T5& arg5, const T6& arg6, const T7& arg7, const T8& arg8, const T9& arg9 ) { Formatter< T1 > f1( arg1 ); Formatter< T2 > f2( arg2 ); Formatter< T3 > f3( arg3 ); Formatter< T4 > f4( arg4 ); Formatter< T5 > f5( arg5 ); Formatter< T6 > f6( arg6 ); Formatter< T7 > f7( arg7 ); Formatter< T8 > f8( arg8 ); Formatter< T9 > f9( arg9 ); const FormatterBase* fs[] = { &f1, &f2, &f3, &f4, &f5, &f6, &f7, &f8, &f9 }; return formatInternal( str, fs, SAL_N_ELEMENTS( fs )); } // testing functionality using namespace rtl; #include <stdio.h> void formattest() { OUString str1( RTL_CONSTASCII_USTRINGPARAM( "test %1 test" )); OUString data1( RTL_CONSTASCII_USTRINGPARAM( "foo" )); char char1[] = "char1"; const char* const char2 = "char2"; const char* char3 = "char3"; const char char4[] = "char4"; OUString res1 = format( str1, data1 ); fprintf(stderr, "1: %s\n", rtl::OUStringToOString( res1, RTL_TEXTENCODING_UTF8 ).getStr()); OUString res2 = format( str1, "foo" ); fprintf(stderr, "2: %s\n", rtl::OUStringToOString( res2, RTL_TEXTENCODING_UTF8 ).getStr()); OUString res3 = format( str1, (short)1 ); fprintf(stderr, "3: %s\n", rtl::OUStringToOString( res3, RTL_TEXTENCODING_UTF8 ).getStr()); // test all char variations format( str1, char1 ); format( str1, char2 ); format( str1, char3 ); format( str1, char4 ); // OUString res4 = format( str1, (void*)0 ); OUString res5 = format( str1, true ); fprintf(stderr, "5: %s\n", rtl::OUStringToOString( res5, RTL_TEXTENCODING_UTF8 ).getStr()); } // testing the resulting assembler OUString sizetest( const OUString& f, const OUString& str ) { return format( f, str ); } // logging functionality based on format() void logfunction( const char* area, const OUString& expression ) { fprintf( stderr, "LOG: %s: %s\n", area, rtl::OUStringToOString( expression, RTL_TEXTENCODING_UTF8 ).getStr()); } inline void logfunction( const char* area, const char* expression ) { fprintf( stderr, "LOG: %s: %s\n", area, expression ); } template< typename T > void logfunction( const char* area, const OUString& str, const T& arg ) { logfunction( area, format( str, arg )); } template< typename T1, typename T2 > void logfunction( const char* area, const OUString& str, const T1& arg1, const T2& arg2 ) { logfunction( area, format( str, arg1, arg2 )); } template< typename T1, typename T2, typename T3 > void logfunction( const char* area, const OUString& str, const T1& arg1, const T2& arg2, const T3& arg3 ) { logfunction( area, format( str, arg1, arg2, arg3 )); } template< typename T > void logfunction( const char* area, const char* str, const T& arg ) { // TODO avoid the OUString conversion? logfunction( area, format( OUString( str, strlen( str), RTL_TEXTENCODING_ASCII_US ), arg )); } template< typename T1, typename T2 > void logfunction( const char* area, const char* str, const T1& arg1, const T2& arg2 ) { logfunction( area, format( OUString( str, strlen( str), RTL_TEXTENCODING_ASCII_US ), arg1, arg2 )); } template< typename T1, typename T2, typename T3 > void logfunction( const char* area, const char* str, const T1& arg1, const T2& arg2, const T3& arg3 ) { logfunction( area, format( OUString( str, strlen( str), RTL_TEXTENCODING_ASCII_US ), arg1, arg2, arg3 )); } #define LOG( area, expression, ... ) \ do \ { \ logfunction( area, expression ,##__VA_ARGS__ ); \ } while( false ) \ void logtest() { OUString str( RTL_CONSTASCII_USTRINGPARAM( "foo" )); OUString form( RTL_CONSTASCII_USTRINGPARAM( "test %1 test" )); LOG( "area1", "fast" ); LOG( "area2", str ); LOG( "area3", form, str ); LOG( "area4", form, 1 ); // LOG( "area5", form, (void*)0 ); LOG( "area6", "test %1 this %2 test", str, false ); } void logsizetest( const OUString& f, const OUString& str ) { LOG( "area1", f, str ); } int main() { formattest(); logtest(); return 0; }
_______________________________________________ LibreOffice mailing list LibreOffice@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/libreoffice