File: distinguish.yo

package info (click to toggle)
c%2B%2B-annotations 11.5.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 11,244 kB
  • sloc: cpp: 21,698; makefile: 1,505; ansic: 165; sh: 121; perl: 90
file content (139 lines) | stat: -rw-r--r-- 7,093 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
In the previous section the tt(BasicTraits) trait class was developed. Using
specialized versions of a nested tt(struct Type) modifiers, pointers,
references and values could be distinguished.

Knowing whether a type is a class type or not (e.g., the type represents a
primitive type) could also be a useful bit of knowledge to a template
developer. The class template developer might want to define a
specialization when the template's type parameter represents a class
type (maybe using some member function that should be available)
and another specialization for non-class types.

This section addresses the question how a trait class can distinguish class
        hi(trait class: class detection)
    types from non-class types.

In order to distinguish classes from non-class types a distinguishing feature
that can be used at compile-time must be found. 
    hi(pointer: to member) It may require some thinking to
find such a distinguishing characteristic, but a good candidate eventually is
found in the pointer to members syntactic construct. Pointers to members
are only available for classes. Using the pointer to member construct as the
distinguishing characteristic, a specialization can be developed that uses the
pointer to member if available. Another specialization (or the generic
template) does something else if the pointer to member construction is not
available.

    How can we distinguish a pointer to a member from `a generic situation',
not being a pointer to a member? Fortunately, such a distinction is
possible. A function template specialization can be defined having a parameter
which is a pointer to a member function. The generic function template
then accepts any other argument. The compiler em(selects) the former
(specialized) function when the provided type is a class type as class types
em(may) support a pointer to a member. The interesting verb here is `em(may)':
the class does not em(have) to define a pointer to member.

    Furthermore, the compiler does not actually em(call) any function: we're
talking compile-time here. All the compiler does is to em(select) the
appropriate function by evaluating a constant expression.

    So, our intended function template now looks like this:
        verb(    template <typename ClassType>
    static `some returntype'  fun(void (ClassType::*)());)

The function's return type (`tt((some returntype))') will be defined
shortly. Let's first have a closer look at the function's parameter.  The
function's parameter defines a pointer to a member returning tt(void). Such a
function does em(not) have to exist for the concrete class-type that's
specified when the function is used.  In fact, em(no) implementation is
provided. The function tt(fun) is only declared as a em(static) member in the
trait class. It's not implemented and no trait class object is required to
call it. What, then, is its use?

    To answer the question we now have a look at the generic function
template that should be used when the template's argument is em(not) a class
type. The language offers a `worst case' parameter in its
        hi(parameter: ellipsis)
    emi(ellipsis) parameter list. The ellipsis is a final resort the compiler
may turn to if everything else fails. The generic function template specifies
a plain ellipsis in its parameter list:
        verb(    template <typename NonClassType>
    static `some returntype' fun(...);)

It would be an error to define the generic alternative as a function
expecting an tt(int). The compiler, when confronted with alternatives,
favors the simplest, most specified alternative over a more complex, generic
one. So, when providing tt(fun) with an argument it selects tt(int)
whenever possible and it won't select tt(fun(void (ClassType::*)())). When
given the choice between tt(fun(void (ClassType::*)())) and tt(fun(...)) it
selects the former unless it can't do that.

    The question now becomes: what argument can be used for both a pointer to
a member and for the ellipsis? Actually, there is such a `one size fits all'
argument: 0. The value 0 can be passed as argument value to functions defining
an ellipsis parameter and to functions defining a pointers-to-member
parameter.

    But 0 does not specify a particular class. Therefore, tt(fun) must specify
an explicit template argument, appearing in our code as tt(fun<Type>(0)), with
tt(Type) being the template type parameter of the trait class.

    Now for the return type. The function's return type cannot be a simple
value (like tt(true) or tt(false)). Our eventual intent is to provide the
trait class with an enum telling us whether the trait class's template
argument represents a class type or not. That enum becomes something like
this:
        verb(    enum { isClass = some class/non-class distinguishing expression } ;)

The distinguishing expression cannot be
        verb(    enum { isClass = fun<Type>(0) } ;)

as tt(fun<Type>(0)) is not a constant expression and enum values em(must)
        hi(enum values: evaluated at compile-time)
 be defined by constant expressions so they can be determined at compile-time.

    To determine tt(isClass)'s value we must find an expression allowing for
compile-time discriminations between tt(fun<Type>(...))  and tt(fun<Type>(void
(Type::*)())).

    In situations like these the ti(sizeof) operator often is our tool of
choice as it is evaluated at compile-time. By defining different sized return
types for the two tt(fun) declarations we are able to distinguish (at
compile-time) which of the two tt(fun) alternatives is selected by the
compiler.

    The tt(char) type is by definition a type having size 1. By defining
another type containing two consecutive tt(char) values a larger type is
obtained. A tt(char [2]) is of course not a type, but a tt(char[2]) can be
defined as a data member of a struct, and a struct em(does) define a
type. That struct then has a size exceeding 1. E.g.,
        verb(    struct Char2
    {
        char data[2];
    };)

tt(Char2) can be defined as a nested type of our traits class. The
two tt(fun) function template declarations become:
        verb(    template <typename ClassType>
    static Char2 fun(void (ClassType::*)());

    template <typename NonClassType>
    static char fun(...);)

Since tt(sizeof) expressions are evaluated at compile-time we can now
determine tt(isClass)'s value:
        verb(    enum { isClass = sizeof(fun<Type>(0)) == sizeof(Char2) };)

This expression has several interesting implications:
    itemization(
    it() no tt(fun) function template is ever instantiated;
    it() the compiler considers tt(Type) and selects
tt(fun)'s function template specialization if tt(Type) is a class type and the
generic function template if not;
    it() From the selected function it determines the return type and thus the
return type's size;
    it() Resulting in the proper evaluation of tt(isClass).
    )
    Without requiring any instantiation the trait class can now provide an
answer to the question whether a template type argument represents a class
type or not. Marvelous!