Boost logo

Boost :

From: Howard Hinnant (hinnant_at_[hidden])
Date: 1999-12-05 16:09:34


John Maddock wrote on 12/5/99 8:06 AM
>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:

Its become apparent to me that I can't live without (at least part of)
the type_traits that I have come up with this past week. But I was just
thinking this morning (before I read John's post) that what I have is
probably neither complete, nor fundamental. After reading John's post, I
feel even more strongly that type_traits will solve a lot of problems,
and that I don't yet have the proper implementation. I also believe that
a complete implementation of type_traits will require compiler help (the
enum problem John speaks of), but it can be approximated fairly nicely
without compiler help .

Here's what I have so far (version shown with imaginary compiler help):

        template <typename T>
        struct type_traits
        {
                static const bool is_class = __is_class(T);
                static const bool has_trivial_default =
__has_trivial_default_constructor(T);
                static const bool has_trivial_copy =
__has_trivial_copy_constructor(T);
                static const bool has_trivial_assign =
__has_trivial_assignment_operator(T);
                static const bool has_trivial_destructor = __has_trivial_destructor(T);
                typedef __strip_top_level_cv(T) cv_unqualified;
        };

The __xxx are "compiler magic" functions that are evaulated at
instantiation time. Without compiler help, this needs to be specialized
for all built-in types, including all of the cv variations. For example:

       template <typename T>
       struct type_traits<T* const volatile>
       {
               static const bool is_class = false;
               static const bool has_trivial_default = true;
               static const bool has_trivial_copy = true;
               static const bool has_trivial_assign = false;
               static const bool has_trivial_destructor = true;
               typedef T* cv_unqualified;
       };

There is some definite intersection between what I've got, and what John
describes.

The is_class member is meant to indicate whether or not I can derive from
an object, and is probably not fundamental. It would include classes,
structs and unions (John's is_composite).

The cv_unqualified typedef is meant to aid in accessing numeric_traits
(or similar traits classes). For example:

template <typename T>
struct has_numeric_traits
{
     static const bool YupGotIt =
numeric_traits<type_traits<T>::cv_unqualified>::is_specialized;
};

>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).

Agreed.

>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>

Yes, perhaps spell "built in type" as "fundamental type". I'm looking at
3.9 in the standard, and the list there looks very similar to your list.
Might be good to stick with the same terminology (if it works).

> 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?

I think this is a very good straw man list.

>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>

I believe I have essentially put this functionality into call_traits (see
below), but correct me if I'm mistaken.

>* 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>
>
>I'm fairly sure that an implementation as above would simplify the
>implementation of call_traits,

I'm sure of this too.

>unfortunately it doesn't quite provide what
>Howard wanted for empty_member - because is_composite<T> appears not to be
>implementable.

Gotta have it. Of course I don't have to have it in Boost. But getting
everything right except enums is a pretty good approximation. I think
we'll need compiler magic to get the enum part right.

>Code for cv-manipulation:

Very cool implementation!

>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?

type_traits/call_traits has become critical in my current work. As the
line between type_traits and call_traits has grown fuzzy (at least for
me), I'd like to show what I'm currently doing for call_traits. It's
effectively doing the "remove reference" operation John referred to.

Consider the following templated example class:

       template <typename T>
       class A
       {
       public:
               A(const T& data) : data_(data) {}
               T return_by_value() const {return data_;}
               T& return_by_reference() {return data_;}
               const T& return_by_const_reference() const {return data_;}
       private:
               T data_;
       };

This represents everything I want to do with (any) type T, call_traits
wise:

1. Pass it in to a method.
2. Return it by value.
3. Return it by reference.
4. Return it by const reference.

Parameterizing the above example with call_traits it looks more like:

       template <typename T>
       class A
       {
       public:
               typedef typename call_traits<T>::value_type
value_type;
               typedef typename call_traits<T>::reference reference;
               typedef typename call_traits<T>::const_reference
const_reference;
               typedef typename call_traits<T>::param_type
param_type;
               A(param_type data) : data_(data) {}
               value_type return_by_value() const {return
data_;}
               reference return_by_reference() {return
data_;}
               const_reference return_by_const_reference() const {return
data_;}
       private:
               T data_;
       };

Test driver code for a simple type might look like:

       T i = 1;
       A<T> a1(i);
       T& i1 = a1.return_by_reference();
       i1 = 2;
       const T& i2 = a1.return_by_const_reference();
       T i3 = a1.return_by_value();

A first stab at the general call_traits struct could look like:

       template <typename T>
       struct call_traits
       {
               typedef T value_type;
               typedef T& reference;
               typedef const T& const_reference;
               typedef const_reference param_type;
       };

A specialization that prevents the reference to a reference problem is
solved with:

       template <typename T>
       struct call_traits<T&>
       {
               typedef T value_type;
               typedef T& reference;
               typedef const T& const_reference;
               typedef reference param_type;
       };

I've got array specializations, and I've also implemented param_type as
value_type for the fundamental types. And there is cv variations for
everything. It's not too lengthy because I've leveraged off of
type_traits and thus don't have "yet another" list of the fundamental
types (using partial specialization on bool as I posted a week ago).

I'm going to try to reconcile some more what I've got with what John
posted this morning.

-Howard


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