1

The solution is obvious, but this is question about nice solution.

(EDIT: by nice I mean e.g. 1) without code redudancy 2) without comprimising performance 3) without forcing programmer to make some unnecessary function or temporary variables )

Consider situation when I would like to execute 3 different blocks of code depending on which of numbers a,b,c is smallest.

The code would look like this:

    if( a < b ){
       if( a < c ){  
           // code block for "a is minimum" case
       }else{        
           // code block for "c is minimum" case
       }
    }else{
       if( b < c ){
           // code block for "b is minimum" case
       }else{    
           // code block for "c is minimum" case
       }
    }

What I don't like is that I have to copy the // code block for "c is minimum" case twice.

There are several solutions to that. E.g. I can put the block of code for "c is minimum" into an inline function or macro. But I don't like it (seems less clear to read).

Old school solution would be use goto like:

    if( a < b ){
       if( a < c ){  
           // code for "a is minimum" case
           goto BRANCHE_END;
       }
    }else{
       if( b < c ){ 
           // code for "b is minimum" case
           goto BRANCHE_END;
       }
    }
   // code for "c is minimum" case
   BRANCHE_END:

but people don't like to see goto (for good reason). On the other hand in this particular case it is even very well readable.

If the block of code would be independent function it can be written like

 void myBranching( double a, double b, double c ){
    if( a < b ){
       if( a < c ){  
           // code for "a is minimum" case
           return;
       }
    }else{
       if( b < c ){ 
           // code for "b is minimum" case
           return;
       }
    }
   // code for "c is minimum" case
    return;
 }

(which is actually almost the same as that goto) But in many cases similar block of code have to be part of more complex algorithm and it is inconvenient to put it inside function. Encapsulation in function would require passing many variables which are used both inside and outside ( e.g. see the use case below) .

Is there any control structure in C/C++ which would solve this in elegant way.

NOTE: Consider performance critical code. (e.g. ray-tracer, GLSL shader, physical simulation ). Anything which would add some unnecessary computational overhead is out of question.


Additional questions / comments

  • This is one of a few examples when I feel like Structured programming tie my hands, and that it is just subset of what is possible to do with jump instruction. Do you know other examples where algorithm would be more simple and clear using goto rather than standard control structures ?
  • can you imagine more complex branching where it would be necessary to copy some blocks of code even more times ?

EDIT : Use case

I think some confusion resulted from the fact that I did not specified context in which I want to use this. This is part of algorithm which raytrace regular triclinic 3D grid ( something like Bresenham's line algorithm in 3D, but the starting point is float (not aligned to center of any box) )

but please, do not focus on algorithm itself, it may be also wrong, I'm currently debugging it.

double pa,pb,pc,invPa,invPb,invPc,mda,mdb,mdc,tmax,t;
int ia,ib,ic; 
// for shortness I don't show initialization of these variables
while( t<tmax ){
    double tma = mda * invPa;
    double tmb = mdb * invPb;
    double tmc = mdc * invPc;
    if( tma < tmb ){
       if( tma < tmc ){  // a min
            t   += tma;
            mda  = 1;
            mdb -= pb*tma;
            mdc -= pc*tma;
            ia++;
       }else{            // c min
            t   += tmc;
            mda -= pa*tmc;
            mdb -= pb*tmc;
            mdc  = 1;
            ic++;
       }
    }else{
       if( tmb < tmc ){  // b min
            t   += tmb;
            mda -= pa*tmb;
            mdb  = 1;
            mdc -= pc*tmb;
            ib++;
       }else{            // c min
            t   += tmc;
            mda -= pa*tmc;
            mdb -= pb*tmc;
            mdc  = 1;
            ic++;
       }
    }
    // do something with ia,ib,ic,mda,mdb,mdc
}
Prokop Hapala
  • 2,424
  • 2
  • 30
  • 59
  • 2
    Is `std::min` what you are looking for? – Jesper Juhl May 23 '16 at 19:30
  • no, it is not. ( btw. I was looking on previously asked questions ... most to the topic is this http://stackoverflow.com/questions/11833327/returning-a-different-result-depending-on-which-of-n-values-is-the-smallest ) – Prokop Hapala May 23 '16 at 19:34
  • The problem with your question is that **nice** is not really defined for everyone the same. – Ivan Aksamentov - Drop May 23 '16 at 19:43
  • @Drop > that is true, but code duplication does not like anybody (I guess) – Prokop Hapala May 23 '16 at 19:50
  • Would you consider this "nice": `return (a < b) ? ( (a < c) ? a : b ) : c;`? – Ivan Aksamentov - Drop May 23 '16 at 20:01
  • In case of more than 3 arguments you just put them into array and loop (min_element). If there are many-many of them you do parallel scan. There is no fundamentally different way to find min of 2 or 3 elements, and I don't see any reason why you dislike your `myBranching` function. – Ivan Aksamentov - Drop May 23 '16 at 20:04
  • I don't like myBranching function because the `code blocks` for case a,b,c operates on some variables which are defined and used outside the decision-tree. So I would need to pass this variables by non-const reference in function header. In that case such encapsulation of the decision-tree does not reflect real boundaries of the algorithm. ( Sorry, that I did not show the whole algorithm, I did not want to complicate things ) – Prokop Hapala May 23 '16 at 21:21

2 Answers2

6

You can solve this pretty easily with std::min and using the version that takes a std::initializer_list. You call min on the three variables to get the minimum and then you have three if statements to check the return against each of the variables.

void foo(int a, int b, int c)
{
    int min = std::min({a, b, c});
    if (min == a)
    {
        // a case
    }
    else if (min == b)
    {
        // b case
    }
    else
    {
        // c case
    }
}
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Nitpick comment: I'd use "else if" since it's only going to be true for one of the branches, so no need to test following if previous matches. But that's a detail. – Jesper Juhl May 23 '16 at 19:35
  • @JesperJuhl Good Idea. I even made the last case just an `else` as there is no other option. – NathanOliver May 23 '16 at 19:36
  • no, thanks, this is doing more comparisons ( >4, I guess 6 in total, while original code does 2 ). I consider perfromance critical code. – Prokop Hapala May 23 '16 at 19:38
  • Doesn't work for the usecase in question as there are `double`s gonna be compared on equality. – Ivan Aksamentov - Drop May 23 '16 at 19:40
  • @Drop if its a double case you can pass in a comparator – dwcanillas May 23 '16 at 19:41
  • 2
    Unless used in a very tight loop there's no way something like this (simple integer comparison) is going to be performance critical. Even in a tight loop I'd trust my compiler to do something sane with it and not bother to try to hand micro optimize it... at least not unless I had profiling data backing it up as being a performance hotspot. – Jesper Juhl May 23 '16 at 19:41
  • 1
    @ProkopHapala You asked for **nice solution** and IMHO this one **is** nice. +1. – CiaPan May 23 '16 at 19:42
  • 1
    @ProkopHapala I don't think you can do better then 1 if statement per variable. – NathanOliver May 23 '16 at 19:42
  • @dwcanillas what comparator? You cannot compare FP types for equality – Ivan Aksamentov - Drop May 23 '16 at 19:45
  • 1
    @Drop you would use a comparator that checks they are close within a tolerance, instead of strict equality. [Here](http://stackoverflow.com/questions/29522171/make-sure-c-decimal-comparison-is-correct/29522205#29522205) is an example. – dwcanillas May 23 '16 at 19:48
  • 1) yes it is double case. 2) the code I wrote evaluates just 2 comparisons (even though there are three `if`s, one of the nested is always cut-off by the first) – Prokop Hapala May 23 '16 at 19:48
  • 1
    @Drop sorry I see what youre saying. Ignore what I said about a comparator. Regardless, you can compare with a tolerance in the if blocks. – dwcanillas May 23 '16 at 19:53
1

You don't have to nest conditionals by the way:

if(a <= b && a <= c) { /* a is the lowest */ }
else if(b <= c) { /* b is the lowest */ }
else { /* c is the lowest */ }

Note that the semantics of C++'s operator && is such that

if(a <= b && a <= c)

works roughly like

if(a <= b) if(a <= c)

(The difference is that the succeeding else clause, if any, covers both ifs simultaneously.) In the expression cond1 && cond2, if cond1 proves to be false then cond2 is never evaluated, at least, it does not have observable side-effects.

Sure we can make something more monstrous, with non-local exits, for example:

do {
    if(a <= b) {
        if(a <= c) {
            // a is the lowest
            break;
        }
    }
    else if(b <= c) {
        // b is the lowest
        break;
    }
    // c is the lowest
}
while(0);

But in fact this construct, despite being incredibly taller, is logically equivalent to those three lines above (with Dieter's proposed edit).

bipll
  • 11,747
  • 1
  • 18
  • 32
  • yes, but this would add still some overhead (2x `<=` instead of one and one `&&`) – Prokop Hapala May 23 '16 at 19:53
  • 3
    Since you seem to be obsessed with performance. Have you looked at the generated asm for any of the suggested solutions? With compiler optimizations enabled of course... my guess is that with a modern compiler (clang 3.8, gcc 6.1 VC++2015) you won't see much (relevant) difference. But do let us know. – Jesper Juhl May 23 '16 at 19:56
  • Or use a profiler to determine that this is actually a bottleneck, which it probably won't be. – dwcanillas May 23 '16 at 20:01
  • @dwcanillas Obviously. This is pointless microoptimization and probably a waste of time. A profiler would most likely point towards much bigger fish to fry. – Jesper Juhl May 23 '16 at 20:02
  • @JesperJuhl There are some more problematic compilers out, like CUDA's nvcc, OpenCL stuff, shader compilers (mentioned in the question). Not sure if it is relevant though. "Nicety" is still __undefined_symbol for me in this problem. – Ivan Aksamentov - Drop May 23 '16 at 20:08
  • @Drop regardless of compiler, priority one should still be "is this performance critical/relevant"? I doubt it. But profiling will tell. – Jesper Juhl May 23 '16 at 20:11