Is there any real reason to use "const"?

H. S. Teoh hsteoh at quickfur.ath.cx
Mon Jan 24 22:40:02 UTC 2022


On Mon, Jan 24, 2022 at 09:41:28PM +0000, jmh530 via Digitalmars-d wrote:
> On Monday, 24 January 2022 at 20:09:46 UTC, H. S. Teoh wrote:
[...]
> > Makes me curious: how feasible is it to have functions with
> > identical bodies merge together as well? This would help reduce
> > template bloat when you have e.g. a templated type where a bunch of
> > functions are identical because they either don't depend on the type
> > (e.g. a container method that doesn't care about what type the
> > payload is), or else depend on type traits that are common across
> > many types (e.g., int.sizeof which is shared with uint.sizeof,
> > float.sizeof, etc.).
[...]
> This reminds me of generics. In languages with generics, you can get
> one copy of a function over many different types. My (possibly
> incorrect) recollection is that it works like having the compiler cast
> the generic MyClass!T to MyClass. Ideally from D's perspective there
> would be a way to do this to minimize any overhead that you would get
> in Java from being forced to use classes.

IMNSHO, Java generics are weaksauce because they are unable to take
advantage of compile-time type information. Basically, once you insert
an object of type T into the container, all information about T is
erased at runtime, it's just a container of Object, so you couldn't, for
example, optimize your container based on the size/alignment of its
contents, for example, or use a more compact storage method by
inspecting the size of T. The container code can only perform operations
that don't introduce a runtime dependency on the specifics of T.

D's templates are much more powerful, but that power does come at the
price of (sometimes great) template bloat: you get a new copy of the
code for every T the template is instantiated with.

The ideal best of both worlds is if the D compiler can somehow
selectively type-erase the implementation of a template, so that the
parts that can be factored out as generic code that works with all T,
of which we only need a single instantiation, vs. the type-dependent
(non-type-erased) parts which remain as separate instantiations. Merging
functions that are binary-identical despite being, at the language
level, distinct template instantiations, would be a good step in this
direction.


> Merging functions with identical bodies is basically the problem that
> inout is trying to solve. A way to express inout via the language
> would probably be good, though inout is rather complicated.  For
> instance, you have an Inout(T) with the property that if you have a
> function that takes an Inout(T), then calling it with Inout!int,
> Inout!(const(int)), and Inout!(immutable(int)) would all have only one
> version of a function at runtime. You would still need to be able to
> enforce that Inout(T) has the same unique behaviors as inout, such as
> that you can't modify the variable in the body of the function.

inout is a hack and a crock. I think it's the wrong approach. First of
all, inout as currently implemented is incomplete: there are a lot of
things you cannot express wrt. inout that you might reasonably want to.
For example, inout applied to delegates: it quickly becomes ambiguous
what the inout is supposed to refer to: the return type of the delegate,
or its parameter, or the outer function's return type, or the outer
function's parameter. Inside the function body if you need to hold
references to the delegate and/or its parameters, it quickly becomes a
total mess (and just plain doesn't compile because the compiler doesn't
understand what you're trying to do and the language doesn't let you
express what you want to do).

Secondly, inout applies only to const/immutable. In generic code, I
frequently find myself wishing for inout(nothrow), inout(pure), etc.,
but the language currently does not support such things. And it's also
questionable whether attribute soup + attribute soup with complete
sub-grammars is really the right direction to go. Your function
declarations quickly drowns in attribute subgrammar and readability goes
out the window.

Third, inout kinda-sorta behaves like a template except that it isn't
one, and it kinda-sorta behaves like Java generics, but without half of
the expressiveness. It's a special case of templates with the
optimization of identical bodies being merged, but artificially
restricted to a single function and to alternation between
const/mutable/immutable, and arbitrarily *not* a template so it behaves
differently from the template part of the language.  A misfit stuck in a
very narrow niche that fits in neither with templates nor with generics.

IMO the right approach is to just replace inout with templates, let the
compiler merge identical function bodies and eliminate template bloat,
and let the compiler infer the attribute soup for you so that you don't
have to deal with it directly.


> This would also be useful for reducing template bloat with allocators.
> For instance, if you have a templated function that takes an allocator
> as a parameter, then for every different allocator you use with it,
> you get an extra copy of the function, even if the body of the
> function may be the same across the different allocators used (since
> it may be jumping to the allocator to call that code).

Exactly.


T

-- 
Skill without imagination is craftsmanship and gives us many useful objects such as wickerwork picnic baskets.  Imagination without skill gives us modern art. -- Tom Stoppard


More information about the Digitalmars-d mailing list