Skip to content

Instantly share code, notes, and snippets.

@TerensTare
Last active April 27, 2026 22:24
Show Gist options
  • Select an option

  • Save TerensTare/38c462dae0920c3640b203b966286751 to your computer and use it in GitHub Desktop.

Select an option

Save TerensTare/38c462dae0920c3640b203b966286751 to your computer and use it in GitHub Desktop.
You only do things once, with C++20.
#include <atomic>
#include <concepts>
#define fwd(x) static_cast<decltype(x) &&>(x)
template <auto = []{}>
inline auto once(std::invocable<> auto &&fn) {
static constinit bool init = false;
if (!init) {
fwd(fn)();
init = true;
}
}
template <auto = []{}>
[[nodiscard]] inline auto once() {
static constinit bool init = false;
bool old = init;
return (init = true), !old;
}
template <auto = []{}>
inline auto once_per_thread(std::invocable<> auto &&fn) {
static thread_local constinit bool init = false;
if (!init) {
fwd(fn)();
init = true;
}
}
template <auto = []{}>
[[nodiscard]] inline auto once_per_thread() {
static thread_local constinit bool init = false;
bool old = init;
return (init = true), !old;
}
template <auto = []{}>
inline auto atomic_once(std::invocable<> auto &&fn) {
static constinit std::atomic_flag init = false;
if (!init.test_and_set(std::memory_order::acquire))
{
fwd(fn)();
std::atomic_thread_fence(std::memory_order::release);
}
}
template <auto = []{}>
inline auto blocking_atomic_once(std::invocable<> auto &&fn) {
// legend: -1 means not init yet, 0 means "is initializing now", 1 means initialized
static constinit std::atomic_int status = -1;
auto tmp = -1;
if (status.compare_exchange_strong(
tmp, 0,
std::memory_order::acq_rel,
std::memory_order::acquire))
{
fwd(fn)();
status.store(1, std::memory_order::release);
status.notify_all();
}
else
status.wait(0, std::memory_order::acquire);
}
#include <cstdio>
#include <thread>
#include <vector>
// For once and once_per_thread, you can also use the alternative overload:
// ```cpp
// if (once())
// {
// }
// ```
int main()
{
int i = 0;
for (; i < 10; ++i)
{
// `once` will run something just once, no matter how many times you go over it.
once([&] {
printf("%d\n", i);
});
}
std::vector<std::jthread> threads;
int state = 0;
for (; i < 20; ++i) {
threads.emplace_back([&, i] {
// `atomic_once` is like `once`, but will only run once in only one thread.
// ^ ie. only one thread calls the function here and everybody else makes progress.
// This means you don't need to protect your data access with a mutex/etc. as long as no other thread is reading/writing the same data by some other means (try replacing `blocking_atomic_once` below with `atomic_once`).
// Note that using `once` here would cause a data race when setting the inner flag.
// This is useful for doing lazy initialization from a single thread.
atomic_once([&] {
printf("i from thread=%d\n", i);
});
// `blocking_atomic_once` offers more safety for certain initialization patterns, by making all threads wait here for `blocking_atomic_once` to finish before making any progress.
// This is useful for cases where
blocking_atomic_once([&] {
for (int j{}; j < 100; ++j) {
state = j * j;
std::this_thread::yield();
}
printf("blocking_atomic_once was done\n");
});
// If we let this run on all threads, we will have a race
// So only the thread with i==15 can do this.
// Notice how it doesn't cause a race with the `blocking_atomic_once`
if (i == 15) {
printf("Modifying `state` after init");
state = i * 100;
}
for (int j{}; j < 10; ++j)
{
// Finally, there is `once_per_thread`, which behaves like `once`, but keeps a separate flag per thread, so each thread runs the callback once.
// This is useful to get `once` behavior, but running on each thread.
// `once` here would cause a data race but also would be ran only once no matter how many threads pass here (assuming no data race happens somehow).
once_per_thread([&] {
auto const tid = std::this_thread::get_id();
auto const hash = std::hash<std::thread::id>{}(tid);
printf("Thread id=%u j=%d\n", (uint32_t)hash, j);
});
}
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment