Boost logo

Boost :

From: John Maddock (John_Maddock_at_[hidden])
Date: 1999-11-25 07:55:25


Beman -

>I'm not sure a separate class is needed. If parameter_traits is
specialized for both <T*> and <const T*>, and for <T&> and <const T&>
per your example, is there really a need for a separate class.<

No there isn't, my old teacher used to say "Think before you ink!", and
it's apparent that I should have listened to him :-)

>Beyond parameter_type, what other typedefs should be included? I
change template_type to decayed_type for now, and removed raw_type.
Need to figure out which would be actually useful and which are just
overspecification.<

I guess the question is how would such a class be used? Or to put it
another way, why do we pass anything by reference rather than value anyway
when the choice is between:
(1)
template <class T>
void foo(const T& t)

and
(2)
template <class T>
void foo(T t)

Clearly if type T is a class with constructors and a destructor, then
passing by reference avoids an unnecessary copy of the object being made,
if foo can be inlined then (1) in this case may prevent inline expansion
(unless the compiler is smart enough to invoke the "as if" rule).

If T is a pointer type, then we gain nothing by passing by reference - and
may actually lose some efficiency as the body of foo will in effect have to
"double dereference" the parameter passed to access its value - whatever
(2) can never be worse than (1) in this case so we may as well use it.

If T is a reference type, then form (1) won't compile (because we can't
create a reference to a reference), this is the case with the standard
library binders for example (which don't work if the functor being bound
takes an argument by reference), so form (2) is mandated here.

If T is a small built in type, then (2) is likely to be the most efficient
option (especially if an immediate value (literal) is being passed to the
function).

If T is an array, then the same arguments as applied to pointer types apply
- and as you pointed out string literals cause problems otherwise.

If T is a POD type or one of the larger built in types (long long, double
etc) its not clear whether (1) or (2) is better. The difference is likely
to small, compiler and machine dependent, so probably we should stick to
the "safe" option and use (1) - this mirrors existing practice - in other
words use of this idiom should never be worse than existing practice.

OK so we have parameter_traits, and use it as:

template <class T>
void foo(typename parameter_traits<T>::parameter_type param)
{
}

Clearly we need the typedef "parameter_type", however the "original type"
which you have as "decayed_type" appears to be unnessary - because of the
way the function is declared we alway have access to this anyway - in this
case as the template parameter T - but even if its some other more complex
type, the body of foo can always tell what it is.

There is one member which may be appropriate though - something like:

static const bool is_specialised = true/false;

So that the function body can tell whether we have converted the "value" to
a reference (is_specialised is false), or whether we have the value
directly (is_specialised is true). To be honest I can't think of any
immediate uses - but I'm sure there are some!

Finally, I can think of two immediate applications for parameter_traits:

To replace const T&, for example in binders/adaptors.

To replace iterators passed by value in algorithms:

currently it is usual to write:

template <class iterator>
void foo(iterator i, iterator j){ /*...*/ }

but if foo can be inlined, and iterator is a class type rather than a
pointer, then we lose effeciency because inline expansion will be
prohibited, so:

template <class iterator>
void foo(typename parameter_traits<iterator>::parameter_type i, typename
parameter_traits<iterator>::parameter_type j){ /*...*/ }

may be better, albeit at the expense of readablity.....

Unfortunately, having experimented with this, template argument deduction
fails in such cases - and thinking about it I can't see how it could have
succeeded, specifying the template parameters explicitly works (as you
would expect), but this isn't really much use - so it looks like the
technique is limited to member functions.

**********

>I also tried specializing on <T[sz]> and <const T[sz]> like this:

template< typename T, int sz >
struct parameter_traits< const T[sz] >
{
    typedef const T * decayed_type;
    typedef const T * parameter_type;
    static const char * which() { return "const T[sz]
specialization"; }
};

This actually works, at least with GCC 2.95.2! Many other compilers
decay the template argument, which they shouldn't, according to
Dietmar Kuehl.<

I tried gcc 2.95.0, and C++ Builder 4, and both use the T* partial
specialisation, althought the presence of the extra code seems to be
harmless in any case, and both worked with:

    char av[3] = { 'a', 'b', 0, };
    single<char*> a1(av);
    single<const char*> a2(av);
    single<const char*> a3("");

Other than side effects as in your single class, is it possible to tell
which partial specialisation gets used - the two seem to be equivalent in
this case??

Sorry this got so long, I hope it makes more sense this time!

- John.


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