-1

I've some goto-laden C++ code that looks like

#include <stdlib.h>
void test0()
{
    int i = 0;
loop:
    i++;
    if (i > 10) goto done;
    goto loop;
done:
    exit(EXIT_SUCCESS);
}

I'd like to get rid of the gotos while (mostly) preserving the appearance of the original code; that more-or-less rules-out for, while etc. (unless they're hidden behind macros) as that changes the appearance of the existing code too much. Imagine wanting to minimize the "diffs" between existing code and changed code.

One idea is to use a class with methods:

class test1 final
{
    int i = 0;
    void goto_loop()
    {
        i++;
        if (i > 10) goto_done();
        goto_loop();
    }
    void goto_done()
    {
        exit(EXIT_SUCCESS);
    }
public:
    test1() { goto_loop(); }
};

This works, but every call to goto_loop() adds to the call stack. Is there some way to do an exec-like function call? That is, call a function "inline" somehow...execute additional code without adding to the call stack? Is there a way to make tail-recursion explicit?

Using C++20 (or even C++23) is acceptable, although a C++17 solution would be "nice."


For all those wondering about "why?" The real original code is BASIC ...

Jack Brown
  • 63
  • 6
  • 7
    why no `while` or `for` loop? – 463035818_is_not_an_ai Jan 05 '22 at 15:08
  • 1
    How about `for (int i = 0; ++i <= 10;) { }`? – Scheff's Cat Jan 05 '22 at 15:09
  • 1
    sorry, but I dont agree on the drama. Your code looks very much like identical to a `for` loop when unrolled in to `goto` with two labels, hence your version with two functions is the more drastic change imho – 463035818_is_not_an_ai Jan 05 '22 at 15:10
  • Turning iterative code into recursive code is usually a mistake. That aside, this seems like a pointless change. If you can't do it right, don't do it. – Pete Becker Jan 05 '22 at 15:11
  • 2
    *"but every call to `goto_loop()` adds to the call stack."*. Not necessary, tail call optimization can apply for that kind of recursion. – Jarod42 Jan 05 '22 at 15:11
  • @JackBrown -- The code you write is only a description of what you want the program to do. The compiler is then free to optimize and change the code in any way it sees fit, as long as the final results are the same, and that includes optimizing the code to do things inline. Playing around with `goto` is not going to have you "beat the compiler" at the speed game. – PaulMcKenzie Jan 05 '22 at 15:12
  • That looks like a do-while loop. Is that a drastic change too? – StoryTeller - Unslander Monica Jan 05 '22 at 15:12
  • @Jarod42 yes...that's what I'm looking for. Is there a way to make that explicit? – Jack Brown Jan 05 '22 at 15:12
  • 2
    @JackBrown You've changed a loop based solution into a recursion based solution. In my opinion this is a significantly more intrusive change than changing the `goto` approach into a `for` or `while` loop. – François Andrieux Jan 05 '22 at 15:14
  • 1
    @JackBrown -- So you are trying to do a line-by-line translation from BASIC to C++? Well, doing line-by-line translations rarely ever work. Why not see what the BASIC code does, and when you figure it out, throw the BASIC code away and rewrite it in C++ using proper C++ form and idioms? Doing line-by-line translations will only result in C++ that is buggy, hard-to-maintain, inefficient, or, in your case, look weird to a C++ programmer. – PaulMcKenzie Jan 05 '22 at 15:17
  • Like a band-aid, sometimes you just need to rip it off an move forward. If you're going to code C++, you should code it like any sane C++ coder would. That'll make it a lot easier for other C++ developers to work with it in the future. – NathanOliver Jan 05 '22 at 15:17
  • If changing the `goto` into a `for` or `while` is too drastic a change, then I recommend leaving the `goto` alone. Legacy code existing is a normal part of an evolving software. – François Andrieux Jan 05 '22 at 15:17
  • @JackBrown Is the aesthetics of the code really the deciding metric for this refactor? – François Andrieux Jan 05 '22 at 15:18
  • @JackBrown I did notice that in the question. But that context doesn't explain why maintaining the original visual appearance of the code is important. – François Andrieux Jan 05 '22 at 15:21
  • @JackBrown How does changing the function to `void test0(){ for (int i = 0; ++i <= 10;) { } exit(EXIT_SUCCESS); }` stop the code from working? – NathanOliver Jan 05 '22 at 15:23
  • @JackBrown -- Here is the flaw in all of this: C++ is **not** BASIC. Trying to make C++ look like BASIC isn't going to get very far. Making C++ into spaghetti like the BASIC program shouldn't be the goal of any programmer. – PaulMcKenzie Jan 05 '22 at 15:25
  • 1
    If I were forced to choose if I'd prefer to implement a for loop with a goto or with a recursion I might choose goto. – al3c Jan 05 '22 at 15:43
  • *I'd like to get rid of the gotos while preserving the appearance of the original code* Then you've set yourself up to fail. I suggest you discard that notion, and do not try to make C++ look like BASIC. – Eljay Jan 05 '22 at 15:46
  • `For all those wondering about "why?" The real original code is BASIC` I don't understand how that would answer the "why?". Why does original code being BASIC mean that the appearance should be preserved? I recommend instead reading this advice from the page that you linked to: *"Please DO update for **modern coding conventions**. .. Use **structured programming**. Use subroutines."* This is a loop. Why not write a structured loop? – eerorika Jan 05 '22 at 17:08
  • @JackBrown -- I'll let you in on an open secret -- C++ code littered with `goto` statements, and then having others look at the code for "bugs" or anything of the sort, reduces the number of persons willing to spend time looking at the code, maybe down to 0. Let's say you continue on, and you encounter more `goto` statements in the BASIC code, where the logic is basically a tangled mess. I will be willing to bet that very few, if anyone will volunteer to make sense of C++ code with the same goto "logic", and will instead try to convince you to rewrite the code. – PaulMcKenzie Jan 05 '22 at 17:58
  • *Imagine wanting to minimize the "diffs" between existing code and changed code.* -- That only makes sense when comparing two pieces of code written in the same language. That doesn't make any sense when comparing two totally different languages. It's like trying to do a "diff" on Spanish and English. – PaulMcKenzie Jan 05 '22 at 18:01
  • Invest your time into writing tests. These will help you much more in future than any changes to syntax you could do now. May be less fun though, depending on your attitude. – anatolyg Jan 05 '22 at 20:30

3 Answers3

2

It sounds like you want to ensure tail call optimization. At present there's no standard way to do this, but if you're using Clang you can use the clang::musttail attribute.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
1

Loop might preserve code structure:

void test2()
{
    int i = 0;
    for (;;) {
        i++;
        if (i > 10) break;
        continue;
    }
    exit(EXIT_SUCCESS);
}

even if

void test3()
{
    for (int i = 0; i < 11; ++i) {
        /*Empty*/
    }
    exit(EXIT_SUCCESS);
}

would be more idiomatic (assuming some useful code as the loop is useless actually).

Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

My solution is to write an exec() routine that stops the recursion:

template<typename Func>
void exec(const Func& f)
{
    using function_t = Func;
    static std::map<const function_t*, size_t> functions;
    const auto it = functions.find(&f);
    if (it == functions.end())
    {
        functions[&f] = 1;
        while (functions[&f] > 0)
        {
            f();
            functions[&f]--;
        }
        functions.erase(&f);
    }
    else
    {
        functions[&f]++;
    }
}

With that utility, I can more-or-less preserve the appearance of the existing code

class test4 final
{
    int i = 0;
    void goto_loop_() {
        i++;
        if (i > 10) goto_done(); } 
    void goto_loop() { goto_loop_(); static const auto f = [&]() { goto_loop(); }; exec(f); }
    void goto_done()
    {
        exit(EXIT_SUCCESS);
    }
public:
    test4() { goto_loop(); }
};

(Using a lambda avoids hassles with pointers to members functions.)

Jack Brown
  • 63
  • 6