[ First ]  [ Previous ]  [ Next ]  [ Last ]  [ Manuals ]

 

Chapter 23.

 

MSL Utiliity



This chapter is a reference guide to the General utility support in the Metrowerks standard libraries.


Overview of MSL Utilities

This chapter consists of utilies for support of non standard headers.


The <msl_utlity> Header

The purpose of this header is to offer a collection of non-standard utilities collected under the namespace Metrowerks. These utilities are of a fundamental nature, and are typically used in other utilities, rather than top level code. Example usage assumes that a using declaration or directive has been previously issued.


NOTE

This header is non-standard The classes herein are offered as extensions to the C++ standard. They are marked as such by the namespace Metrowerks.


Concepts and ideas co-developed on Boost


  http://www.boost.org/

Basic Compile-Time Transformations

A collection of templated structs which can be used for simple compile-time transformations of types.


remove_const

Will remove the top level const (if present) from a type.

Prototype:

typedef typename 
  remove_const<T>::type non_const_type;

The resulting "non_const_type" will be the same as the input type T, except that if T is const qualified, that const-ness will be removed.

Example of remove_const:


typedef typename remove_const <const int>::type Int;
Int has type int.


remove_volatile

Will remove the top level volatile (if present) from a type.

Prototype:

typedef typename 
  remove_volatile<T>::type non_volatile_type;

The resulting "non_volatile_type" will be the same as the input type T, except that if T is volatile qualified, that volatile-ness will be removed.

Example of remove_volatile:


typedef typename remove_volatile <volatile int>::type Int;
Int has type int.


remove_cv

Will remove the top level qualifiers (const and/or volatile, if present) from a type.

Prototype:

typedef typename 
  remove_cv<T>::type non_qualified_type;

The resulting "non_qualified_type" will be the same as the input type T, except that if T is cv qualified, the qualifiers will be removed.

Example of remove_cv:


typedef typename remove_cv <const int>::type Int;
Int has type int.


remove_pointer

If given a pointer, returns the type being pointed to. If given a non-pointer type, simply returns the input.

Prototype:

typedef typename 
  remove_pointer<T>::type pointed_to_type;
Example of remove_pointer:
typedef typename 
remove_pointer<const int*volatile*const>::type IntPtr;
typedef typename remove_pointer<IntPtr>::type Int;





IntPtr will have type type const int*volatile. Int will have the type const int.

 



remove_reference

 

If given a reference, returns the type being referenced. If given

a non-reference, simply returns the input.

Prototype:

typedef typename 

  remove_reference<T>::type referenced_type;


Example of remove_reference:


typedef typename remove_reference<int&>::type Int;
typedef typename remove_reference<const int&>::type ConstInt;





Int has the type int, and ConstInt has the type const int.

 



remove_bounds

 

If given an array type, will return the type of an element in

the array. If given a non-array type, simply returns the input.

Prototype:

typedef typename remove_bounds<T>::type Element;

Example of remove_bounds:


typedef int IntArray[4];
typedef typename remove_bounds<IntArray>::type Int;





Int has the type int.

 



remove_all

 

This transformation will recursively remove cv qualifiers, pointers,

references and array bounds until the type is a fundamental type,

enum, union, class or member pointer.

Prototype:

typedef typename remove_all<T>::type 
fundamental_type;

Example of remove_all:


typedef const int** Array[4];
typedef typename remove_all<Array*&>::type Int;





Int has the type int.

 



Type Query

 

The following structs perform basic queries on one or more types

and return a bool value.


is_same

 

This struct can be used to tell if two types are the same type

or not.

Prototype:

bool b = is_same<T, U>::value;

Example of is_same:


bool b = is_same<const int, int>::value;
The resulting value is false. int and const int are two distinct types.

 



CV Query


is_const

 

Returns true if type has a top level const qualifier, else false.

Prototype:

bool b = is_const<T>::value;

Example of is_const:


bool b = is_const<const int>::value;
The resulting value is true.

 



is_volatile

 

Returns true if type has a top level volatile qualifier, else

false.

Prototype:

bool b = is_volatile<T>::value;

Example of is_volatile:


bool b = is_volatile<const int>::value;
The resulting value is false.

 



Type Classification

 

The following structs implement classification as defined by

section 3.9 in the C++ standard. All types can be classified into

one of ten basic categories:

 

1. integral

 

2. floating

 

3. void

 

4. pointer

 

5. member pointer

 

6. reference

 

7. array

 

8. enum

 

9. union

 

10. class

 

Top level cv qualifications do not affect type classification.

For example, both const int and int are considered to be of integral

type.

Prototype:

bool b = is_XXX<T>::value;

where XXX is one of the ten basic categories.

 

1. is_integral

 

2. is_floating

 

3. is_void

 

4. is_pointer

 

5. is_member_pointer

 

6. is_reference

 

7. is_array

 

8. is_enum

 

9. is_union

 

10. is_class

Example of is_integral:


bool b = is_integral<volatile int>::value;
The value of b is true.

 


The classifications: is_enum and is_union do not currently work

automatically. Enums and unions will be mistakenly classified

as class type. This can be corrected on a case by case basis by

specializing is_enum_imp or is_union_imp. These specializations

are in the Metrowerks::details namespace.

Example of Metrowerks::details namespace:


enum MyEnum {zero, one, two};
 
template <>
struct Metrowerks::details::is_enum_imp<MyEnum>
	{static const bool value = true;};

 


Now MyEnum will be correctly classified as an enum instead of

a class (via the is_enum struct). You do not need to worry about

providing this specialization unless you are explicitly using

the is_enum query and wanting your enumeration to answer to it

correctly.

 

There are also five "super" categories that are made up of combinations

of the ten basic categories:

 

1. is_arithmetic - is_integral or is_floating

 

2. is_fundamental - is_arithmetic or is_void

 

3. is_scalar - is_arithmetic or is_pointer or is_member_pointer

or is_enum

 

4. is_compound - not is_fundamental

 

5. is_object - anything but a void or reference type

 

is_extension is also provided for those types that we provide

as an extension to the C++ standard. is_extension<T>::value will

be false for all types except for long long and unsigned long

long.

 

has_extension is a modified form of is_extension that answers

to true if a type either is an extension or contains an extension.

Example of is_extension and has_extension:


is_extension<long long*&>::value;   // false
has_extension<long long*&>::value;  // true

 



is_signed / is_unsigned

 

These structs only work on arithmetic types. The type must be

constructable by an int and be less-than comparable.

Example of is_signed and is_unsigned use:


bool b1 = is_signed<char>::value;
bool b2 = is_unsigned<char>::value;

 



NOTE

At the risk of restarting a great debate, bool tests as unsigned



POD classification

 

Four structs classify types as to whether or not they have trivial

special members as defined in section 12 of the C++ standard:

 

  • has_trivial_default_ctor

  • has_trivial_copy_ctor

  • has_trivial_assignment

  • has_trivial_dtor

This library will answer correctly for non-class types. But user

defined class types will always answer false to any of these queries.

If you create a class with trivial special members, and you want

that class to be able to take advantage of any optimizations that

might arise from the assumption of trivial special members, you

can specialize these structs:

Example of specialized structs:


template <>
struct Metrowerks::details::class_has_trivial_default_ctor<MyClass>
	{static const bool value = true;};
 
template <>
struct Metrowerks::details::class_has_trivial_copy_ctor<MyClass>
	{static const bool value = true;};
 
template <>
struct Metrowerks::details::class_has_trivial_assignment<MyClass>
	{static const bool value = true;};
 
template <>
struct Metrowerks::details::class_has_trivial_dtor<MyClass>
	{static const bool value = true;};

 


Note that these specializations need not worry about cv qualifications.

The higher level has_trival_XXX structs do that for you.

 

Finally there is an is_POD struct that will answer true if a

type answers true on all four of the above queries.


Miscellaneous


compile_assert

 

This is a compile time assert. This is a very basic version of

this idea. Can be used to test assumptions at comile time.

Example of compile_assert use:


#include <msl_utility>
 
template <class T>
T
foo(const T& t)
{
	Metrowerks::compile_assert<sizeof(T) >= sizeof(int)> T_Must_Be_At_Least_As_Big_As_int;
	//...
	return t;
}
 
int main()
{
	int i;
	foo(i); // ok
	char c;
	foo(c); // Error   : illegal use of incomplete struct/union/class
	        //           'Metrowerks::compile_assert<0>'

 



array_size

 

Given an array type, you can get the size of the array with array_size.

Example usage of array_size:


typedef int Array[10];
size_t n = array_size<Array>::value;
n has the value of 10.

 



can_derive_from

 

A simple union of class type and union types.

Prototype:

bool b = can_derive_from<T>::value;


store_as - Container optimization

 

Starting with Pro 4.1 the standard sequences vector, deque and

list implemented the "void* optimization" as described in section

13.5 of Stroustrup's 3rd. In a nutshell, this optimization allows

all Container<T*> to share an implementation with Container<void*>

for the purpose of reducing template code bloat.

 

Starting with Pro 6 containers will take this idea further and

with more flexibility. Using store_as, one can specify what types

can be stored as alternate types in the containers. For example,

a vector<unsigned char> can be implemented as a vector<char>,

thus saving on the instantiation of vector<unsigned char>. Only

vector uses store_as in Pro 6, but other containers will have

this capability in future releases.

Prototype:

template <> struct store_as<Type_to_be_optimized>

   {typedef Implementaiton_Type type;};


For example, to specify that a container<long> be implemented

in terms of a container<unsigned long>:


  template <> struct store_as<long>

  {typedef unsigned long type;};


If a specialization of store_as does not appear for a type, then

that type will be implemented as itself in containers.

 

This header has a default table of store_as specializations suitable

for your platform. But if for some reason you are not happy with

the shipping configuration, you can alter the behavior here. Additionally,

this optimization can be turned off by #define'ing _Inhibit_Container_Optimization

in <mslconfig>, or in a prefix file.

 

The requirements for a type to appear in a store_as specialization

are:

 

  • It must have a trivial copy constructor, assignment operator and

    destructor.

  • Its default constructor must do nothing but cause all bytes in

    the object to be zeroed (if this constructor is used).

  • The two types in a store_as specialization must have the same

    sizeof.

User defined types can also take advantage of this optimization

if they meet the above requirements (recommended only for those

that really know what they are doing). Client code must declare

that the user defined type is a POD, and then create a store_as

entry. For example, here is a struct that will share its vector

implementation with vector<unsigned long>:

A struct that shares its vector implementation:


#include <msl_utility>
#include <vector>
 
struct A
{
	int data_;
};
 
template <> struct Metrowerks::is_POD<A>   
		{static const bool value = true;};
template <> struct Metrowerks::store_as<A> 
		{typedef unsigned long type;};
 
int main()
{
	std::vector<A> a(5);  
// Shares implementation with vector<unsigned long>
}

 


Note that this is strictly a code size optimization. Functionality

does not change. And it only saves on code size if you already

have to use a vector<unsigned long> for some other reason (or

if you are using a vector that is stored as a vector<unsigned

long>).


call_traits

 

This struct is a collection of typedef's that ease coding of

template classes when the template parameter may be a non-array

object, an array, or a reference. The typedef's specify how to

pass a type into a function, and how to pass it back out either

by value, reference or const reference. The interface is:


  call_traits<T>::value_type

  call_traits<T>::reference


  call_traits<T>::const_reference


  call_traits<T>::param_type


The first three types are suggestions on how to return a type

from a function by value, reference or const reference. The fourth

type is a suggestion on how to pass a type into a method.

 

The call_traits struct is most useful in avoiding references

to a reference which are currently illegal in C++. Another use

is in helping to decay array-type parameters into pointers. In

general, use of call_traits is limited to advanced techniques,

and will not require specializations of call_traits to be made.

For example uses of call_traits see compressed_pair. For an example

specialization see alloc_ptr.


is_empty

 

Answers true if the type is a class or union that has no data

members, otherwise answers false. This is a key struct for determining

if the space for an "empty" object can be optimized away or not.

Prototype:

bool b = is_empty<T>::value;


compressed_pair

 

Like std::pair, but attempts to optimize away the space for either

the first or second template parameter if the type is "empty".

And instead of the members being accessible via the public data

members first and second, they are accessible via member methods

first() and second(). compressed_pair handles reference types

as well as other types thanks to the call_traits template. This

is a good example to study if you're wanting to see how to take

advantage of either call_traits or is_empty. To see an example

of how compressed_pair is used see alloc_ptr.

Synopsis of compressed_pair:


template <class T1, class T2>
class compressed_pair<T1, T2>
{
public:
	typedef T1                                                 first_type;
	typedef T2                                                 second_type;
	typedef typename call_traits<first_type>::param_type       first_param_type;
	typedef typename call_traits<second_type>::param_type      second_param_type;
	typedef typename call_traits<first_type>::reference        first_reference;
	typedef typename call_traits<second_type>::reference       second_reference;
	typedef typename call_traits<first_type>::const_reference  first_const_reference;
	typedef typename call_traits<second_type>::const_reference second_const_reference;
 
	compressed_pair();
	compressed_pair(first_param_type x, second_param_type y);
	explicit compressed_pair(first_param_type x);
	explicit compressed_pair(second_param_type y);
 
	first_reference       first();
	first_const_reference first() const;
 
	second_reference       second();
	second_const_reference second() const;
 
	void swap(compressed_pair& y);
};
 
template <class T1, class T2> void swap
	(compressed_pair<T1, T2>& x, compressed_pair<T1, T2>& y);

 


Use of the single argument constructors will fail at compile

time (ambiguous call) if first_type and second_type are the same

type.

 

The swap specialization will call swap on each member if and

only if its size has not been optimized away. The call to swap

on each member will look both in std, and in the member's namespace

for the appropriate swap specialization. Thus clients of compressed_pair

need not put swap specializations into namespace std.

 

A good use of compressed_pair is in the implementation of a container

that must store a function object. Function objects are typically

zero-sized classes, but are also allowed to be ordinary function

pointers. If the function object is a zero-sized class, then the

container can optimize its space away by using it as a base class.

But if the function object instantiates to a function pointer,

it can not be used as a base class. By putting the function object

into a compressed_pair, the container implementor need not worry

whether it will instantiate to a class or function pointer.

Example of compressed_pair:


#include <iostream>
#include <functional>
#include <msl_utility>
 
template <class T, class Compare>
class MyContainer
{
public:
	explicit MyContainer(const Compare& c = Compare()) : data_(0, c) {}
 
	T*             pointer()            {return data_.first();}
	const T*       pointer() const      {return data_.first();}
	Compare&       compare()            {return data_.second();}
	const Compare& compare() const      {return data_.second();}
	void           swap(MyContainer& y) {data_.swap(y.data_);}
private:
	Metrowerks::compressed_pair<T*, Compare> data_;
};
 
int main()
{
	typedef MyContainer<int, std::less<int> >    MyContainer1;
	typedef MyContainer<int, bool (*)(int, int)> MyContainer2;
	std::cout << sizeof(MyContainer1) << '\n';
	std::cout << sizeof(MyContainer2) << '\n';
}

 


MyContainer1 uses a zero-sized Compare object. On a 32 bit machine,

the sizeof MyContainer1 will be 4 bytes as the space for Compare

is optimized away by compressed_pair. But MyContainer2 instantiates

Compare with an ordinary function pointer which can't be optimized

away. Thus the sizeof MyContainer2 is 8 bytes.


alloc_ptr

 

An extension of std::auto_ptr. alloc_ptr will do everything that

auto_ptr will do with the same syntax. Additionally alloc_ptr

will deal with array new/delete:

Alloc_ptr will deal with array new/delete:


alloc_ptr<int, array_deleter<int> > a(new int[4]);  
// Ok, destructor will use delete[]

 


By adding the array_deleter<T> template parameter you can enable

alloc_ptr to correctly handle pointers to arrays of elements.

 

alloc_ptr will also work with allocators which adhere to the

standard interface. This comes in very handy if you are writing

a container that is templated on an allocator type. You can instantiate

an alloc_ptr to work with an allocator with:


alloc_ptr<T, Allocator<T>, typename Allocator<T>::size_type> a;

 


The third parameter can be omitted if the allocator is always

going to allocate and deallocate items one at a time (e.g. node

based containers).

 

alloc_ptr takes full advantage of compressed_pair so that it

is as efficient as std::auto_ptr. The sizeof(alloc_ptr<int>) is

only one word. Additionally alloc_ptr will work with a reference

to an allocator instead of an allocator (thanks to call_traits).

This is extremely useful in the implementation of node based containers.

Synopsis of class alloc_ptr:


template<class T, class Allocator = single_deleter<T>,
	class Size = number<call_traits<Allocator>::value_type::size_type, 1> >
class alloc_ptr
{
public:
	typedef T element_type;
 
	typedef typename call_traits<Allocator>::value_type      allocator_type;
	typedef typename call_traits<Allocator>::param_type      allocator_param_type;
	typedef typename call_traits<Allocator>::reference       allocator_reference;
	typedef typename call_traits<Allocator>::const_reference allocator_const_reference;
	typedef typename allocator_type::size_type               size_type;
	typedef typename allocator_type::difference_type         difference_type;
	typedef typename allocator_type::pointer                 pointer;
	typedef typename allocator_type::const_pointer           const_pointer;
	typedef typename allocator_type::reference               reference;
	typedef typename allocator_type::const_reference         const_reference;
 
	explicit alloc_ptr(pointer p = 0);
	alloc_ptr(pointer p, allocator_param_type alloc, Size sz = Size());
	alloc_ptr(alloc_ptr& x);
	template<class U>
		alloc_ptr(alloc_ptr<U, allocator_type::rebind<U>::other, Size>& x);
	~alloc_ptr();
	alloc_ptr& operator =(alloc_ptr& x);
	template<class U>
		alloc_ptr& operator=(alloc_ptr<U, allocator_type::rebind<U>::other, Size>& x);
	reference operator*() const;
	pointer operator->() const;
	reference operator[](difference_type n);
	reference operator[](size_type n);
	const_reference operator[](difference_type n) const;
	const_reference operator[](size_type n) const;
	pointer get() const;
	pointer release();
	void reset(pointer p = 0, Size size);
	alloc_ptr(alloc_ptr_ref<T, Allocator, Size> r);
	alloc_ptr& operator=(alloc_ptr_ref<T, Allocator, Size> r);
	template<class U> operator alloc_ptr_ref<U, allocator_type::rebind<U>::other, Size>();
	template<class U> operator alloc_ptr<U, allocator_type::rebind<U>::other, Size>();
	allocator_reference       allocator();
	allocator_const_reference allocator() const;
	Size&      get_size();
	size_type  capacity() const;
};

 


This is essentially the std::auto_ptr interface with a few twists

to accommodate allocators and size parameters.

 

 

 

 


[ First ]  [ Previous ]  [ Next ]  [ Last ]  [ Manuals ]

Visit the Metrowerks website at: http://www.metrowerks.com
For assistance contact Metrowerks Technical Support at: cw_support@metrowerks.com
Copyright © 2000, Metrowerks Corp. All rights reserved.

Last updated: July 21, 2000