ChatGPT解决这个技术问题 Extra ChatGPT

Most elegant way to write a one-shot 'if'

Since C++ 17 one can write an if block that will get executed exactly once like this:

#include <iostream>
int main() {
    for (unsigned i = 0; i < 10; ++i) {

        if (static bool do_once = true; do_once) { // Enter only once
            std::cout << "hello one-shot" << std::endl;
            // Possibly much more code
            do_once = false;
        }

    }
}

I know I might be overthinking this, and there are other ways to solve this, but still - is it possible to write this somehow like this, so there is no need of the do_once = false at the end?

if (DO_ONCE) {
    // Do stuff
}

I'm thinking a helper function, do_once(), containing the static bool do_once, but what if I wanted to use that same function in different places? Might this be the time and place for a #define? I hope not.

Why not just if (i == 0)? It's clear enough.
@SilvanoCerza Because that is not the point. This if-block might be somewhere in some function that gets executed multiple times, not in a regular loop
maybe std::call_once is an option (it's used for threading, but still does it's job).
Your example may be a bad reflection of your real-world problem which you aren't showing us, but why not just lift the call-once function out of the loop?
It didn't occur to me that variables initialized in an if conditions could be static. That's clever.

A
Acorn

Use std::exchange:

if (static bool do_once = true; std::exchange(do_once, false))

You can make it shorter reversing the truth value:

if (static bool do_once; !std::exchange(do_once, true))

But if you are using this a lot, don't be fancy and create a wrapper instead:

struct Once {
    bool b = true;
    explicit operator bool() { return std::exchange(b, false); }
};

And use it like:

if (static Once once; once)

The variable is not supposed to be referenced outside the condition, so the name does not buy us much. Taking inspiration from other languages like Python which give a special meaning to the _ identifier, we may write:

if (static Once _; _)

Further improvements: take advantage of the BSS section (@Deduplicator), avoid the memory write when we have already run (@ShadowRanger), and give a branch prediction hint if you are going to test many times (e.g. like in the question):

// GCC, Clang, icc only; use [[likely]] in C++20 instead
#define likely(x) __builtin_expect(!!(x), 1)

struct Once {
    bool b = false;
    explicit operator bool()
    {
        if (likely(b))
            return false;

        b = true;
        return true;
    }
};

I know macros get a lot of hate in C++ but this just looks so damn clean: #define ONLY_ONCE if (static bool DO_ONCE_ = true; std::exchange(DO_ONCE_, false)) To be used as: ONLY_ONCE { foo(); }
i mean, if you write "once" three times, then use it more than three times in if statements it is worth it imo
The name _ is used in a lot of software to mark translatable strings. Expect interesting things to happen.
If you have the choice, prefer the initial value of static state to be all-bits-zero. Most executable-formats contain the length of an all-zero area.
Using _ for the variable there would be un-Pythonic. You don't use _ for variables that will be referenced later, only to store values where you have to provide a variable but you don't need the value. It's typically used for unpacking when you only need some of the values. (There are other use cases, but they're quite different from the throwaway value case.)
l
lubgr

Maybe not the most elegant solution and you don't see any actual if, but the standard library actually covers this case:, see std::call_once.

#include <mutex>

std::once_flag flag;

for (int i = 0; i < 10; ++i)
    std::call_once(flag, [](){ std::puts("once\n"); });

The advantage here is that this is thread safe.


I wasn't aware of std::call_once in that context. But with this solution you need to declare a std::once_flag for every place you use that std::call_once, don't you?
This works, but wasn't meant for a simple if solution, the idea is for multithreaded applications. It's overkill for something that simple because it uses internal synchronization - all for something that would be solved by a plain if. He was not asking for a thread safe solution.
@MichaelChourdakis I agree with you, it's an overkill. However, it's worth knowing about, and especially knowing about the possibility to express what you're doing ("execute this code once") instead of hiding something behind a less readable if-trickery.
Uh, seeing call_once to me means you want to call something once. Crazy, I know.
@SergeyA because it uses internal synchronization - all for something that would be solved by a plain if. It's a rather idiomatic way of doing something other than what was asked for.
S
Sebastian Mach

C++ does have a builtin control flow primitive that consists of "(before-block; condition; after-block)" already:

for (static bool b = true; b; b = false)

Or hackier, but shorter:

for (static bool b; !b; b = !b)

However, I think any of the techniques presented here should be used with care, as they are not (yet?) very common.


I like the first option (though - like many variants here - it is not thread safe, so be careful). The second option gives me chills (harder to read and may be executed any number of times with only two threads... b == false: Thread 1 evaluates !b and enters for loop, Thread 2 evaluates !b and enters for loop, Thread 1 does its stuff and leaves the for loop, setting b == false to !b i.e. b = true... Thread 2 does its stuff and leaves the for loop, setting b == true to !b i.e. b = false, allowing the whole process to be repeated indefinitely)
I find it ironic that one of the most elegant solutions to a problem, where some code is supposed to be executed only once, is a loop. +1
I would avoid b=!b, that's looks nice, but you actually want the value to be false, so b=false shall be preferred.
Be aware that the protected block will run again if it exits non-locally. That might even be desirable, but it’s different from all the other approaches.
B
Bathsheba

In C++17 you can write

if (static int i; i == 0 && (i = 1)){

in order to avoid playing around with i in the loop body. i starts with 0 (guaranteed by the standard), and the expression after the ; sets i to 1 the first time it is evaluated.

Note that in C++11 you could achieve the same with a lambda function

if ([]{static int i; return i == 0 && (i = 1);}()){

which also carries a slight advantage in that i is not leaked into the loop body.


I'm sad to say - if that'd be put in a #define called CALL_ONCE or so it'd be more readable
While static int i; might (I'm truly not sure) be one of those cases where i is guaranteed to be initialized to 0, it's much clearer to use static int i = 0; here.
Regardless I agree an initialiser is a good idea for comprehension
@Bathsheba Kyle didn't, so your assertion has already been proven false. What does it cost you to add two characters for the sake of clear code? Come on, you're a "chief software architect"; you should know this :)
If you think spelling out the initial value for a variable is the contrary of clear, or suggests that "something funky is happening", I don't think you can be helped ;)
W
Waxrat
static bool once = [] {
  std::cout << "Hello one-shot\n";
  return false;
}();

This solution is thread safe (unlike many of the other suggestions).


Did you know () is optional (if empty) on lambda declaration?
m
moooeeeep

You could wrap the one-time action in the constructor of a static object that you instantiate in place of the conditional.

Example:

#include <iostream>
#include <functional>

struct do_once {
    do_once(std::function<void(void)> fun) {
        fun();
    }
};

int main()
{
    for (int i = 0; i < 3; ++i) {
        static do_once action([](){ std::cout << "once\n"; });
        std::cout << "Hello World\n";
    }
}

Or you may indeed stick with a macro, that may look something like this:

#include <iostream>

#define DO_ONCE(exp) \
do { \
  static bool used_before = false; \
  if (used_before) break; \
  used_before = true; \
  { exp; } \
} while(0)  

int main()
{
    for (int i = 0; i < 3; ++i) {
        DO_ONCE(std::cout << "once\n");
        std::cout << "Hello World\n";
    }
}

O
Oreganop

Like @damon said, you can avoid using std::exchange by using a decrementing integer, but you have to remember that negative values resolve to true. The way to use this would be:

if (static int n_times = 3; n_times && n_times--)
{
    std::cout << "Hello world x3" << std::endl;
} 

Translating this to @Acorn's fancy wrapper would look like this:

struct n_times {
    int n;
    n_times(int number) {
        n = number;
    };
    explicit operator bool() {
        return n && n--;
    };
};

...

if(static n_times _(2); _)
{
    std::cout << "Hello world twice" << std::endl;
}

D
Damon

While using std::exchange as suggested by @Acorn is probably the most idiomatic way, an exchange operation is not necessarily cheap. Although of course static initialization is guaranteed to be thread-safe (unless you tell your compiler not to do it), so any considerations about performance are somewhat futile anyway in presence of the static keyword.

If you are concerned about micro-optimization (as people using C++ often are), you could as well scratch bool and use int instead, which will allow you to use post-decrement (or rather, increment, as unlike bool decrementing an int will not saturate to zero...):

if(static int do_once = 0; !do_once++)

It used to be that bool had increment/decrement operators, but they were deprecated long ago (C++11? not sure?) and are to be removed altogether in C++17. Nevertheless you can decrement an int just fine, and it will of course work as a Boolean condition.

Bonus: You can implement do_twice or do_thrice similarly...


I tested this and it fires multiple times except for the first time.
@nada: Silly me, you're right... corrected it. It used to work with bool and decrement once upon a time. But increment works fine with int. See online demo: coliru.stacked-crooked.com/a/ee83f677a9c0c85a
This still has the problem that it might get executed so many times that do_once wraps around and will eventually hit 0 again (and again and again...).
To be more precise: This would now get executed every INT_MAX times.
Well yes, but apart from the loop counter also wrapping around in that case, it's hardly a problem. Few people run 2 billion (or 4 billion if unsigned) iterations of anything at all. If they do, they could still use a 64-bit integer. Using the fastest available computer, you'll die before it wraps around, so you cannot be sued for it.
N
Nick Louloudakis

Based on @Bathsheba's great answer for this - just made it even simpler.

In C++ 17, you can simply do:

if (static int i; !i++) {
  cout << "Execute once";
}

(In previous versions, just declare int i outside the block. Also works in C :) ).

In simple words: you declare i, which takes default value of zero (0). Zero is falsey, therefore we use exclamation mark (!) operator to negate it. We then take into account the increment property of the <ID>++ operator, which first gets processed (assigned, etc) and then incremented.

Therefore, in this block, i will be initialized and have the value 0 only once, when block gets executed, and then the value will increase. We simply use the ! operator to negate it.


If this would have been posted earlier, it'd most probably be the accepted answer now. Awesome, thanks!