const assignments problem again

bearophile bearophileHUGS at lycos.com
Sat Aug 6 18:19:19 PDT 2011


I have discussed about this topic once in past, but in the meantime I have seen this is a quite common problem, so I think it doesn't harm to touch this topic again.

This is a direct D translation of the original C or C++ code:


// version #1
double foo;
if (abs(e.x - v.x) > double.min)
    foo = (v.y - e.y) / (v.x - e.x);
else
    foo = double.max;


This version is clear, easy to understand, efficient, and it's not bug-prone. Some coding standards require those to use full braces there. But it has some problems too:
- It's not DRY, "foo" is repeated three times;
- You can't use "auto" there, so if the type of e.x changes, you have to change the foo type manually (double.min is replaceable with typeof(e.x).min).
- It's not short code.
- And foo can't be const or immutable, I don't like this.


In this specific example one of the two branches of the if contains just a constant, so you are allowed to write:

// version #2
double foo = double.max;
if (abs(e.x - v.x) > double.min)
    foo = (v.y - e.y) / (v.x - e.x);


But generally you can't do that because the then-else branches of the if are meant to be computed lazily, only one of them.


To turn foo constant, you are free to use a temporary mutable variable, but this makes the local namespace even more dirty:

// version #3
double _blue;
if (abs(e.x - v.x) > double.min)
    foo = (v.y - e.y) / (v.x - e.x);
else
    foo = double.max;
immutable foo = _blue;


In D you are allowed to create a function and call it in place:

// version #4
const foo = {
    if (abs(e.x - v.x) > double.min)
        return (v.y - e.y) / (v.x - e.x);
    else
        return double.max;
}();


Version #4 has some downsides:
- The return type of this delegate is inferenced. This means that both branches of the if must return the same type. Currently in D this disallows some possibilities (I hope this will be fixed), in some cases you have to cast the empty result to the same type of the other if branch;
- The code is even longer and I don't like its look a lot (in JavaScript it's fine);
- It's not certain that every D compiler will always inline that delegate. This risks lower performance;
- That delegate uses values defined outside it, so it can't be pure, so the function that contains such code can't be pure. You solve this problem making the code more complex, passing the needed local variables as arguments to the delegate, but this is not handy.


Version #5 is an acceptable solution, it is compact and it defines just one variable, that is constant, but the C conditional expression is bug-prone and it's a bit tricky (it's a common source of various bugs), I don't like it. This code is less maintainable (if you want to add something you sometimes need to convert it again into a normal if). 

// version #5
const foo = (abs(e.x - v.x) > double.min) ?
             ((v.y - e.y) / (v.x - e.x)) :
             double.max;


In my precedent post about this topic I have discussed "nested constness" and another partially silly idea. Recently I have seen a language that suggests me this:

// version #6
const foo;
if (abs(e.x - v.x) > double.min)
    foo = (v.y - e.y) / (v.x - e.x);
else
    foo = double.max;


The compiler makes sure all paths assign values with the same type to foo (as in the case of the two returns inside the delegate, that must be of the same type). But if you introduce goto instructions version #6 looks fragile. If the assign is far away from the definition the code looks not so nice any more, so this feature is meant for short-range initializations only.

Bye,
bearophile


More information about the Digitalmars-d mailing list