Boost logo

Boost :

From: John Maddock (John_Maddock_at_[hidden])
Date: 1999-12-05 08:06:41


Its become apparent to me that the version of SGI's type_traits.h that I
have been using is not sufficiently flexible for my needs - Steve's
proposal for type-traits gets much closer to the mark, but I can't help
wondering if we can get closer still.

I've been trying to figure out what the minimal requirement for some sort
of type traits concept should be, in fact some generic design rules for
traits classes in general, I'm not sure if this is complete but here goes
anyway:

There are three categories of traits class, which can be described as
follows:

1) Property traits:
Does some type T have some property P? Generally expressed as:

template <typename T>
struct P { static const bool value = true/false; }

Property traits are in general the most fundamental of all traits types -
user defined property-traits should never have to specialise for built-in
types - they should instead be implemented "in terms of" some fundamental
set of property traits (whatever these may be - see later).

2) Transformation traits:
Given some Type/Value as input, transform it to some other Type/value
according to some well defined rule:

template <typename T>
struct rule{ typedef application_of_rule type; }

Like property traits, there is probably some fundamental set of
transformations from which almost all others can be synthesised, again like
property traits it should not be necessary to specialise these for all
built-in types.

3) Baggage classes:
A baggage class applies some customization to a template class, by
supplying the baggage class as a template parameter to the client class. An
example would be std::char_traits<>. In general these should not need to
be specialised for built-ins (although they often are), but rather
generically programmed in terms of standard property and transformation
traits.

*****

OK lets suppose that some "fundamental set" of property/transformation
traits does in fact exist, there are a couple of design goals we should try
to obey:

A) No two traits classes should duplicate the same functionality - using
the definitions above, that should be easy to achieve since each class
effectively concentrates on just one task. However we already have some
traits classes in the standard library - and since we're not about to
rewrite that, let's assume that std::numeric_limits<T> is a "fundamental"
property traits class.

B) We should minimise the number of traits class specialisations in the
library - by keeping the fundamental set as small as possible - and
implementing any other traits classes in terms of that set.

So the question becomes - what fundamental questions can we ask about a
type?
The following list seems to cover most bases - those marked * are
fundamental (not implemented in terms of another class), those marked ?
seem to be non-implementable.

* is a type T a built in type - is_builtin<T>
   is a type T an arithmetic/integral/real type - is_signed_int<T>
is_unsigned_int<T> is_int<T> is_real<T> - implemented in terms of
numeric_limits<T>.
* is a type T a reference type - is_reference<T>
* is a type T a pointer type (including function pointers) - is_pointer<T>
*? is a type T a regular function pointer - is_function_pointer<T>
* is a type T a member function pointer - is_member_pointer<T>
* is a type T declared const - is_const<T>
* is a type T declared volatile - is_volatile<T>
* is a type T an array - is_array<T>
* is a type T the same as type U - is_same_type<T,U>
  is a type T a UDT - is_UDT<T> - the inverse of is_builtin<T>, maybe
unnecessary.
*? is a type T an enum - is_enum<T>
*? is a type T a composite type (class/struct/union) - is_composite<T> -
can be implemented if is_enum<T> can be (and vice-versa).
* is a type T an empty composite type - is_empty<T> - only strictly
implementable if we have is_composite<T>
   is a type T a POD type - is_POD<T> - by default implemented in terms of
is_builtin<T> - can be specialised for UDT's.

The implementation of the above is either obvious or follows Steve's code:
except for is_const and is_volatile - code for which follows. Those
marked ? pose more of a problem, any ideas gratefully received, also are
there any fundamental type properties that are missing?

We can also construct a similar list for type transformations:

* convert a type T to a reference if its not already one - add_reference<T>
* convert a type T to a non-reference if it is one - remove_reference<T>
* convert a type T to non-const type - remove_const<T>
* convert a type T to a non-volatile type - remove_volatile<T>
  convert a type T to a non-cv-qualified type - remove_qualification<T>

Again code follows Steve's except for the cv-qualifier code which follows,
note that there are no add_const or add_volatile templates as these are in
effect trivial. Again this is not necessarily a complete list.

I'm fairly sure that an implementation as above would simplify the
implementation of call_traits, unfortunately it doesn't quite provide what
Howard wanted for empty_member - because is_composite<T> appears not to be
implementable. It would also be of great help to me in simplifying some of
the baggage classes that I'm currently working with, but what about every
one else? What have I missed? Or would anyone else use this?

- John.

Code for cv-manipulation:

namespace _Jm{
//
// implementation helper:
//
template <class T>
struct cv_traits_imp{};

template <class T>
struct cv_traits_imp<T*>
{
   static const bool is_const = false;
   static const bool is_volatile = false;
   typedef T non_const_type;
   typedef T non_volatile_type;
   typedef T unqualified_type;
   static const char* what() { return ""; }
};

template <class T>
struct cv_traits_imp<const T*>
{
   static const bool is_const = true;
   static const bool is_volatile = false;
   typedef T non_const_type;
   typedef T non_volatile_type;
   typedef T unqualified_type;
   static const char* what() { return "const"; }
};

template <class T>
struct cv_traits_imp<volatile T*>
{
   static const bool is_const = false;
   static const bool is_volatile = true;
   typedef T non_const_type;
   typedef T non_volatile_type;
   typedef T unqualified_type;
   static const char* what() { return "volatile"; }
};

template <class T>
struct cv_traits_imp<const volatile T*>
{
   static const bool is_const = true;
   static const bool is_volatile = true;
   typedef volatile T non_const_type;
   typedef const T non_volatile_type;
   typedef T unqualified_type;
   static const char* what() { return "const volatile"; }
};

} // namespace _Jm

template <typename T>
struct is_const
{
   static const bool value = _Jm::cv_traits_imp<T*>::is_const;
};

template <typename T>
struct is_volatile
{
   static const bool value = _Jm::cv_traits_imp<T*>::is_volatile;
};

template <typename T>
struct remove_volatile
{
   typedef typename _Jm::cv_traits_imp<T*>::non_volatile_type type;
};

template <typename T>
struct remove_const
{
   typedef typename _Jm::cv_traits_imp<T*>::non_const_type type;
};

template <typename T>
struct remove_qualification
{
   typedef typename _Jm::cv_traits_imp<T*>::unqualified_type type;
};

//
// test code:
//
int main()
{
    cout << is_const<int>::value << endl;
    cout << is_const<const int>::value << endl;
    cout << is_const<volatile int>::value << endl;
    cout << is_const<const volatile int>::value << endl;
    cout << is_volatile<int>::value << endl;
    cout << is_volatile<const int>::value << endl;
    cout << is_volatile<volatile int>::value << endl;
    cout << is_volatile<const volatile int>::value << endl;
}
//


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk