In this document I describe details of the synthesis process results of which are presented in Class-type Enumeration. I recommend that you read the introductory discussion from the aforementioned article if you are not familiar with the problem.
Traditionally, people rely on their intuition and experience when it comes to designing new things. When engineers are asked how they design, the answer is usually, "We don't know, we just do". With such an approach, however, we can't be sure we've got the best solution for the effort. Also mere engineering doesn't scale once we start undertaking more ambitious projects with large sets of conflicting criteria to meet.
While trying to resolve numerous problems of C++ enumerations, I realized that I need a more formal method of solution seeking which will allow me to (a) handle the complexity of multiple objectives (b) formally show that I've got optimal solution(s). To achieve this I used the process of optimal synthesis which is based on the theory of multi-criteria optimization.
The method can be described as a number of steps:
The first step is to create structural-parametric model. Based on the desired properties of the construct, I came up with the following scheme. Each alternative is described by the vector
a = (t, e, p, s, i, c)
where t, e, p, s, i, c
are from the corresponding sets
T = {class(0), enum(1), int(2)} E = {class(0), enum(1), int(2)} P = {class(0), name(1)} S = {class(0), enum(1), int(2)} I = {none(0), explicit(1), implicit(2)} C = {none(0), explicit(1), implicit(2)}
t
is an enumeration type. For example when t = class
the enumeration type is a class-type. e
is an
enumerator type. p
is an enumerator's qualifier name.
p = class
means that t = class
and enumerators
are qualified with this name (e.g. Color::red
). p = name
means that enumerators are qualified with a name which is achieved
via placing enumerators inside a namespace or a struct. s
is a
type of switch labels. The model allows separate constants of type
s
to be defined for switch support. If e
and
s
have the same value, then enumerators are used for switch
labels. i
determines the type of integral promotion/conversion
supported by t
. Similarly c
determines the type of
conversion to char const*
. Jokingly, you can view this model as
the following pseudo-C++ declaration.
struct t == class ? p : null { t Color { e red, green, blue; if (e != s) { s red_l, green_l, blue_l; } conversion (i, int); conversion (c, char const*); }; };
Where conversion
expands to a conversion operator,
conversion function or nothing depending on its arguments. Note also
that this model contains a good number of impossible alternatives that
we will have to take care of later.
If you read until here you must be wondering how I came up with this
model. First of all, this is the third-generation model in the sense that
I performed optimal synthesis three times and the results from the first
two were used to shape the model. Another source of ideas for the model is
criteria system. We can view each criterion as a function from the space of
alternatives to the space of positive numbers plus 0
.
0
means that the alternative does not meet the criterion. Other
numbers indicate how well the alternative meets the criterion (the higher the
number the better). Now, if we can build a reciprocal function of our
criterion function and collect all the values of this function for all the
arguments except 0
, we will get a complete set of alternatives
which can possibly meet the criterion. Performing this operation for each
criterion in criteria system we can get an idea about what the complete
alternative set should look like.
The next step is to define the criteria system.
Encapsulation of enumerators. The criterion function is defined as follows:
C(a) : t == 0 -> 2 p == 1 -> 1 0
By giving p = 1
case value 1
instead of
2
I accounted for the fact that encapsulation with the
enumeration type is preferred to the encapsulation with some other name.
Default or enforced initialization. The only way to get default or enforced initialization in C++ is by using class constructor. Thus, the criterion function looks like this:
C(a) : t == 0 -> 2 0
Labels. The ability to retrieve labels for enumerators depends on
the conversion to char const*
in our model. Thus, the criterion
function is defined as
C(a) : c
Banning meaningless usage. In order to make meaningless operations on enumeration instances illegal, the enumeration type should not have implicit conversion to integral or pointer type, plus enumerator's type should not be of integral type:
C(a) : i != 2 && c != 2 && e == 0 -> 2 0
Usable with switch control statement. This criterion can be
divided into two sub-criteria: the enumeration type should be convertible
to integral type and switch label type should be of integral type. Also
we prefer enumerator and switch label to be the same thing
(s == e
).
C(a) : i == 2 && s != 0 && s == e -> 4 i == 2 && s != 0 -> 3 i == 1 && s != 0 && s == e -> 3 i == 1 && s != 0 -> 2 0
As you may have noticed none of our criteria express preference to
enum
over int
or vice versa. Based on this
observation we can combine them into one parameter which will make
the result analysis more manageable:
T = {class(0), integral(1)} E = {class(0), integral(1)} P = {class(0), name(1)} S = {class(0), integral(1)} I = {none(0), explicit(1), implicit(2)} C = {none(0), explicit(1), implicit(2)}
Having structural-parametric model and criteria system ready we can proceed to optimal synthesis. I wrote a small synthesis program that generates the alternative set based on the model, performs optimization of this set and dumps the result:
$ ./synth 132 alternatives before optimization (0, 0, 0, 1, 1, 1) (2, 2, 1, 2, 2) (0, 1, 0, 1, 2, 2) (2, 2, 2, 0, 4)
As you can see the optimized set contains two alternatives. I invite you to further discuss the meaning of the results in Class-type Enumeration.
Copyright © 2003, 2004 Boris Kolpackov. See license for conditions.
Last update on Jan 26 2004