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.lunak@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;
}
Context
- [Libreoffice] Simpler logging using a string format function · Lubos Lunak
Privacy Policy |
Impressum (Legal Info) |
Copyright information: Unless otherwise specified, all text and images
on this website are licensed under the
Creative Commons Attribution-Share Alike 3.0 License.
This does not include the source code of LibreOffice, which is
licensed under the Mozilla Public License (
MPLv2).
"LibreOffice" and "The Document Foundation" are
registered trademarks of their corresponding registered owners or are
in actual use as trademarks in one or more countries. Their respective
logos and icons are also subject to international copyright laws. Use
thereof is explained in our
trademark policy.