Re: Integral and non-integral

From: Dietmar Kuehl (dietmar_kuehl_at_yahoo.com)
Date: 03/09/04


Date: Tue, 09 Mar 2004 21:44:49 +0100

Alf P. Steinbach wrote:
> Pete Becker insists that there was a rationale, and that he were present
> (and perhaps active) in the relevant committee discussions, but refuses
> to divulge any information except that the integral types give compile
> time constants and according to him are the only compile time constants in
> C++ and that somehow but secret secret that was related to the decision.

> Pete, any more?

It seems to be a very clever move to start off insulting people when
you want them to help you: I think Pete's approach towards an answer
is a quite reasonable one - at least from his point of view.

Although I'm not Pete and I'm pretty sure that I wasn't involved in the
discussion about static integral members, I still might be able to shed
some light on the issue although I'm pretty sure that what I'm going to
say is not what you want to hear. The short answer to the question in
this thread is pretty simple:

Of course, it would have been possible to require the ability of
initializing static const members, or actually all members, in the class
definition. However, this was not the issue! It was at no point desired
to do any such thing at all. On the contrary, there was an absolute
and unavoidable need to allow initialization of static const members of
integral type: it was considered necessary that such objects participate
in constant propagation, ie. that they are handled, when initialized,
like constant expressions. That is, you got it backwards: it is not an
advantage to allow initialization in the class declaration, it is a
necessity for integral types.

Now for the longer answer to the issue... I don't know whether you
ever programmed C but it is important to understand that a considerable
amount of C's power is derived from the separation of declaration and
definition. You had one or more headers which just declared the entities
shared between multiple translation units and even when you made changes
to the implementation there was no need to recompile everything. Only
when you needed to change anything in a header, it became necessary to
recompile the stuff depending on the header. In times when compiling a
source file took several seconds or even minutes it was an important
feature that you could change the implementation in a large project
without having to recompile everything.

Now, do you already see where this is going? Well, C++ is firmly rooted
in the C tradition and many of the people who worked on and with C++ came
from a C background. That is, it was and is a goal of C++ to separate
declaration from definition to get an efficient, or at least a viable,
development process: what is part of the implementation should not affect
other translation units and cause them to be recompiled. Unfortunately,
this goal was dwarved by certain features of the OO approach and
later, due to completely different reasons, by the introduction of
templates. First lets address the OO approach which introduced the
need for inline functions: In many cases, encapsulation means that
you get small functions which do just a tiny little thing. Rather
than having big functions which do several things and deal with low
level issue you get all those small functions. The effect of this is
catastrophic to performance: it becomes necessary to save and restore
function contexts, put data into the appropriate places to pass them
to or from a function, and the optimizer became just tiny bits of code
it could not do much about. As a consequence, it became necessary to
compromise on the separation of declaration and definition and to allow
inline functions. The idea here is that the inline functions just do
small bit of trivial stuff which is hard to get wrong and hence there is
only rare need to touch them. Inline functions are bad, the alternative,
ie. mediocre performance, would be worse.

Templates are an even harder problem. If every translation unit using
a template sees the definition, each translation unit can create the
necessary code and place it into a section where the linker does not
complain about multiple definitions (where code for not really inlined
inline code is also placed; of course, this should never happen because
it gets the worst situation possible: code is generated multiple times,
an additional dependency is created, and there is no gain at all).
Since this multiple code generation is undesirable (it just takes time
and space for no good reason) and causes tight coupling, compiler vendors
tried to avoid this coupling (well, at least SUN did; I don't know of
any other early compiler which used a similar scheme for templates) and
introduced the separation model. At that time, nobody thought that things
would come out that bad (ironically, EDG was one of the few opponents to
the separation model and is now the first compiler supporting it). Anyway,
the important issue here is, that it was attempted to even separate
template declaration and implementation. Apart from the automatic approach
of the separation model, the standard also supports a manual instantiation
approach which allows to separate template declaration and definition,
even if this approach is only viable for cases where there is a small
number of [expected] template arguments the template is used with (the
IOStreams and locales libraries are good examples here). In any case, it
was necessary to support multiple template definitions to have a viable
approach. Again, the choice is between two evils: tighter coupling vs. no
templates at all, where the latter was not viable at all and hence the
choice fell on coupling.

This gives the context in which the issue of static const members was
discussed: clearly, this stuff should be kept out of the way and put
into the definition file. It simply has no use in the header and just
increases coupling - a bad thing. Has it? Well, there is a performance
and even semantic issue when it comes down to integral expresssions:
these can be computed at compile time and used correspondingly in cases
where constants can be used, eg. to define the size of a member array
in a struct. The C++ attitude towards performance was and is to support
idioms yielding the highest performance possible and hence it became
necessary to allow initialization of static integral constant members
in the declaration. Again, two evils - low performance or tighter
coupling - again the choice fell in favour of performance, accepting
the tighter coupling.

It is worth pointing out that the coupling issue was not the only one
considered. Another important issue at least was the requirement to use
C++-unaware system linkers: these were not capable of removing duplicate
definitions (they just copied whole object files from libraries) and
caused conflicts when multiple definitions of the same object occured.
This was not an issue with static constant integral members because they
are still required to be defined only once. Just their value can be seen
in multiple translation units and used without causing a new symbol to be
created. After the standard was finalized, during resolution of defects,
the requirement for a C++ aware linker was, however, finally introduced:
static variables in inline functions need to be unique.

In summary, coupling is bad: if you change the implementation of one
class, no other class' implementation should be affected. It is bad
enough that other code is affected by interface changes (ie. any change
in members, whether public, protected, or private, or bases, etc.) but
this is a tribute to the C++'s value semantics which is important to
the performance of certain uses. Initializing static const members
increases coupling and there is no point in supporting it in general,
there is only reason for the integral case.

You may disagree with this view, of course. Personally, I have seen
more than one project fail due to excessive coupling. Since I'm using
templates quite a lot, I'm clearly no extremist towards coupling but
is it definitely an issue I'm concerned about. In any case, this is at
least one of the reasons why integral static constant members can be
initialized and other static constant members cannot.

-- 
<mailto:dietmar_kuehl@yahoo.com> <http://www.dietmar-kuehl.de/>
Phaidros eaSE - Easy Software Engineering: <http://www.phaidros.com/>


Relevant Pages

  • Re: Terms for method types?
    ... In such model we have a template of some kind to produce object ... common descriptions as bringing too much of parasite context, ... initial set of members. ... implying any particular programming language and ii) being meaningful ...
    (comp.lang.javascript)
  • Re: Remote Web Workplace permission template
    ... Where in the Server Management Console are you? ... members of isn't right. ... Select the User template ... In members of tab, there is power> user template but no remote web workplace even though the ...
    (microsoft.public.windows.server.sbs)
  • Re: Shared Workspace document doesnt show the status of on line m
    ... I have the same issue and think I have worked out why members appear not to ... area created from any template other than inheriting form the parent template ... then online collaboration will not work. ... workspace area that shows Online and Not Online members status. ...
    (microsoft.public.sharepoint.portalserver)
  • Re: C++ pushback
    ... There are all sorts of macros that use member initialization of that form. ... Since the kernel uses a lot of data structures to strictly define binary formats for transmission to hardware, across a network, or to userspace, such changes can cause nothing but heartache. ... If they were not, one could simply make a base class having members outlined, and which class does not enforce type safety and is for inheritance only. ... This ugly-looking code can be nicely wrapped into a template, which, depending on the type, at compile time, picks the proper stage for initialization. ...
    (Linux-Kernel)