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

前端 未结 9 1993
情话喂你
情话喂你 2020-12-22 21:51

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

#include 

        
相关标签:
9条回答
  • 2020-12-22 21:53

    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.

    0 讨论(0)
  • 2020-12-22 22:00
    static bool once = [] {
      std::cout << "Hello one-shot\n";
      return false;
    }();
    

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

    0 讨论(0)
  • 2020-12-22 22:10

    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;
        }
    };
    
    0 讨论(0)
  • 2020-12-22 22:12

    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";
        }
    }
    
    0 讨论(0)
  • 2020-12-22 22:12

    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;
    }
    
    0 讨论(0)
  • 2020-12-22 22:12

    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...

    0 讨论(0)
提交回复
热议问题