Friday, December 25, 2009

Episode Thirteen: The Ways Of sizeof

C++ inherits from C the sizeof operator; it yields the number of bytes in the object representation of its operand. It comes in two flavours, `sizeof( type-name )` and `sizeof expression`. It's important to note that expressions in the form `sizeof( ptr )->member` refer to the second sizeof form, and are not a syntactic error.

While sizeof is just about getting the size of its operand, its power extends beyond that.
When operated on expressions, every regular rule applies yet the expression is not evaluated. Particularly, when applying sizeof to an overloaded function call, overload resolution rules apply. This means that, if the resulting types of the overloaded functions differ in size, sizeof can effectively be used to discover the would-be called function from an overloaded function set. The following example shows how:
typedef char (&int_overload_type)[1];
typedef char (&double_overload_type)[2];

int_overload_type f( int );
double_overload_type f( double );

// All three expressions evaluate to true
sizeof( f( 1 ) ) == sizeof( int_overload_type );
sizeof( f( 1. ) ) == sizeof( double_overload_type );
sizeof( f( 1.f ) ) == sizeof( double_overload_type );
This technique is particularly useful to 'introspect' characteristics of a generic type when working with templates. The full power of overload resolution is available at compile time via the 'sizeof trick'; this includes ellipsis, template functions and even SFINAE [1]. Following is a possible implementation of the `is_convertible` trait:
template< typename T, typename U >
struct is_convertible
{
    typedef char (&yes_type)[1];
    typedef char (&no_type)[2];

    static T& make();

    static yes_type test( U const& );
    static no_type test( ... );

    static const bool value =
        sizeof( test( make() ) ) == sizeof( yes_type );
};

Since any standard or user defined conversion is preferred over the ellipsis, the size of the overloaded function call will result in that of yes_type when a conversion from T to U exists. (Note: Passing user defined types through ellipsis results in undefined behaviour, and some compilers will issue a warning even when the call is never invoked. Furthermore, access rules are applied after overloading resolution, so this technique will trigger a compile time error if the conversion is achieved via a protected/private user defined conversion operator).

In the same way, traits can be built to detect structural properties (dependant names, based on SFINAE), operator overloading (involving the 'comma operator trick'), etc.

Generally speaking whatever selection can be achieved by overload resolution, can be obtained at compile time by means of sizeof.

[1] http://en.wikipedia.org/wiki/SFINAE

No comments:

Post a Comment