Boost logo

Boost :

From: Tobias Schwinger (tschwinger_at_[hidden])
Date: 2004-09-20 11:13:52


Terje Slettebø wrote:
>>From: "Terje Slettebø"
>
>
>>>From: "Tobias Schwinger"
>>
>>>I attached some experimental source code for the prefiltering of an
>>>operator+ check to clearify the basic idea (compiles with GCC and MSVC
>>>and CVS version of boost).
>>
>>It works indeed with GCC and MSVC, but Intel C++ 7.1 had some serious
>>problems with it. The following is one of several of the same kind of
>

Terje,

first of all thanks for your time and for your corrections.

However, I still think there is nothing wrong with the basic idea, just
with my sloppy example implementation of it. I should definitely buy a
more conformant compiler to recognize such things before...

With some refinement (see below) this could be a very attractive (also
see below) alternative to the "metaprogramming boilerplate" used to
force things into shape.

Refinement of the example code:
-------------------------------

The type categorization could be done like this:

   struct integral_or_enum { };
   struct real_number { };
   struct pointer_or_array { };
   struct class_or_union { };

   class_or_union type_category(...);

   integral_or_enum type_category(char);
   integral_or_enum type_category(short);
   integral_or_enum type_category(int);
   integral_or_enum type_category(long);

   real_number type_category(float);
   real_number type_category(double);

   template <typename T>
   pointer_or_array type_category(T const *);

Used like this:

   BOOST_STATIC_CONSTANT(int, state = ( sizeof(filter(type_category(arg1)
                                              ,type_category(arg2) )) ));

This way the ambiguity problems vanish (tested the overloaded cascade
with Comeau-online, just in case ;+).

Problem discussion:
-------------------

> Another problem with this approach can be demonstrated by listing the cases
> that are checked by the operator+ detection:
>
> - Both types being arithmetic or enum, or

I renamed the type category 'integral' to 'integral_or_enum' because of
7.2-8 (enum), 4.5-2 (integral promotion).

> - One is array and the other is integral or enum, or

I renamed the type category 'pointer' to 'integral_or_array' because of
13.3.3.1.1 (overloading - standard conversion sequences) Tab. 9.

If there is a need to distiguish between (object) pointer and array this
needs extra care (see below), sure. However, has_plus_op applies these
tests disjunctively.

> - Either is class, and the other is anything but void

A match defines an exclusion - no match means "pass the filter" - and
this would correctly happen in this case.

> Even if we found a way to give a numerical code or something to each
> fundamental type (typical typeof-implementation), there's still enum, so we
> need some kind of is_enum-detection (which would likely require the Boost
> one). The same goes for detecting class or union. We also need a way to
> express the above conditions. I haven't found a shorter way of specifying
> this, than the current has_plus_op<> definition
> (operator_traits/has_plus_op.hpp).

Can't see an "is_enum" - only "is_enum_or_integral" and this is much easier.

Can't see any distinction between class and union in has_plus_op.hpp,
either.

The strenght of the overload approach lies in the natural way of
grouping types - can't see what separation of integral types should help.

I'm not sure I get your point.

Am I making any naive assumptions here ? Am I misreading anything ?

Please, correct me if I'm wrong !

Real world scenario / further suggestions:
------------------------------------------

1

A "real world implementation" will need yet another filtering stage:

The argument creation (currently only add_reference is applied) can be
changed to achieve this (so in place of just add_reference *any*
metafunction could be applied in its place).

A tagging type can be created for exceptional cases which can then be
caught with a type_category overload.

In fact, I believe there is no way around it, if the behaviour of these
traits should be defined for function and member pointers. With this
approach it is checked only _once_, however.

2

It is possible to group the category tagging types by
inheritance in addition to using namespaces to elegantly arrange the
rule set (as mentioned in the previous post):

// Note: this is just an example to clearify the text
// it's *not* a very good grouping here
struct anything { };
struct class_or_union : anything { };
struct numeric_or_pointer : anything { };
struct numeric : numeric_or_pointer { };
struct pointer_or_array : numeric_or_pointer { };
struct integral_or_enum : numeric { };
struct real_number : numeric { };

3

All operators of one group having the same prefiltering rules can share
one filter. The filter implementation can be shared, too - _excpet_ the
sizeof expression and a using statement to select the rule set.

Why is this approach more attractive than the current implementation ?
----------------------------------------------------------------------

1. It exploits the very nature of the type system instead of brute
forcing around it in order to emulate it afterwards

These metafunctions exclude cases that are naturally easier to detect
together (like class/union, enum/integral).
Afterwards they are or-ed so the exclusion (which did cost us probably
more metafunction invocations than the actual detection) has been done
for nothing.

Nothing wrong with this - but not very efficient if all we want from
them is a model of categories natural to the C++ type system.

Overthis, some metafunction might contain lengthy workarounds for
specific compiler and every one of them tests for cv qualified void.

2. It depends on just a few headers ("driver code" and rule set)

Specification of portability becomes much easier.

The circumstances under which a specific metafunction works become
harder to track the more transitive dependencies arise.

3. It is resource friendly

It takes up less compiler memory, needs less filesystem access and is
likely to be less complex.
These aspect will especially carry weight for the compilation time of
projects split into a lot of small compilation units.

4. It is easier portable

Once the "driver code" runs correctly on a platform, _all_ filtering runs.
Because it does depend on just a few metafunctions it's less tedious
to check their documentation/source if they exist/work/return the right
result on the target compiler.

Best regards,

Tobias


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