c++ if-statement c++17

c++ - La forma más elegante de escribir un one-shot ''if''



if-statement c++17 (8)

C ++ tiene una primitiva de flujo de control incorporada que consiste en "( antes del bloque; condición; después del bloque )" ya:

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

O hackier, pero más corto:

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

Sin embargo, creo que cualquiera de las técnicas presentadas aquí debe usarse con cuidado, ya que no son (¿aún?) Muy comunes.

Desde C ++ 17 se puede escribir un bloque if que se ejecutará exactamente una vez como esto:

#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; } } }

Sé que podría estar pensando demasiado en esto, y hay otras maneras de resolverlo, pero aún así, ¿es posible escribir esto de esta manera, por lo que no hay necesidad de do_once = false al final?

if (DO_ONCE) { // Do stuff }

Estoy pensando en una función auxiliar, do_once() , que contiene el static bool do_once , pero ¿y si quisiera usar esa misma función en diferentes lugares? ¿Podría ser este el momento y el lugar para un #define ? Espero que no.


Como dijo @damon, puedes evitar el uso de std::exchange mediante el uso de un entero decreciente, pero debes recordar que los valores negativos se resuelven como verdaderos. La forma de usar esto sería:

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

Traducir esto al envoltorio de lujo de @ Acorn se vería así:

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; }


En C ++ 17 puedes escribir

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

para evitar jugar con i en el cuerpo del loop. i comienza con 0 (garantizado por el estándar), y la expresión después de ; establece i en 1 la primera vez que se evalúa.

Tenga en cuenta que en C ++ 11 podría lograr lo mismo con una función lambda

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

lo que también conlleva una ligera ventaja en que no se filtra hacia el cuerpo del bucle.


Podría envolver la acción de una sola vez en el constructor de un objeto estático que ejemplifique en lugar del condicional.

Ejemplo:

#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"; } }

O bien, puede seguir con una macro, que puede verse algo así:

#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"; } }


Si bien el uso de std::exchange como lo sugiere @Acorn es probablemente la forma más idiomática, una operación de intercambio no es necesariamente barata. Aunque, por supuesto, se garantiza que la inicialización estática es segura para subprocesos (a menos que le diga a su compilador que no lo haga), por lo que cualquier consideración sobre el rendimiento es algo inútil de todos modos en presencia de la palabra clave static .

Si le preocupa la microoptimización (como suele suceder con la gente que usa C ++), también podría rascar bool y usar int lugar, lo que le permitirá usar el post-decremento (o más bien, incrementar , como a diferencia de bool decrementing a int will no saturar a cero ...):

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

Solía ​​ser que bool tenía operadores de incremento / decremento, pero estaban en desuso hace mucho tiempo (¿C ++ 11? ¿No estoy seguro?) Y deben eliminarse por completo en C ++ 17. Sin embargo, puede disminuir un int simplemente bien, y por supuesto funcionará como una condición booleana.

Bono: Puedes implementar do_twice o do_thrice manera similar ...


Tal vez no sea la solución más elegante y no vea ninguna real, pero la biblioteca estándar cubre este caso: vea std::call_once .

#include <mutex> std::once_flag flag; for (int i = 0; i < 10; ++i) std::call_once(flag, [](){ std::puts("once/n"); });

La ventaja aquí es que esto es seguro para subprocesos.


Utilice std::exchange :

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

Puedes hacerlo más corto invirtiendo el valor de verdad:

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

Pero si estás usando esto mucho, no seas lujoso y crea una envoltura en su lugar:

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

Y utilízalo como:

if (static Once once; once)

Se supone que la variable no debe ser referenciada fuera de la condición, por lo que el nombre no nos compra mucho. Inspirándonos en otros idiomas como Python que dan un significado especial al identificador _ , podemos escribir:

if (static Once _; _)

Mejoras adicionales: aproveche la sección BSS (@Deduplicator), evite la escritura en memoria cuando ya lo hayamos ejecutado (@ShadowRanger), y brinde un consejo de predicción de ramificación si va a realizar pruebas muchas veces (por ejemplo, como en la pregunta):

// 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; } };


static bool once = []() { std::cout << "Hello one-shot/n"; return false; }();

Esta solución es segura para subprocesos (a diferencia de muchas de las otras sugerencias).