En el mundo de la programación en C, garantizar que ciertas expresiones sean constantes durante la compilación es una práctica que aporta robustez y puede prevenir errores inesperados en tiempo de ejecución. Detectar si una expresión es constante es un reto interesante que puede aumentarse en complejidad o facilidad dependiendo de la versión del estándar C y las extensiones del compilador a las que se tenga acceso. Identificar si una expresión es constante significa verificar que su valor se conozca en tiempo de compilación, es decir, que el compilador pueda evaluarla y fijar su resultado, optimizando el código y asegurando comportamientos predecibles. A lo largo de los años han surgido metodologías y trucos ingeniosos para conseguir este propósito, algunos aprovechando características más modernas del estándar, otras utilizando extensiones específicas de compiladores como GCC o Clang. Una aproximación reciente y elegante involucra el uso de las nuevas funcionalidades introducidas en C23, que incluyen soporte para el almacenamiento constexpr y el operador typeof.
Gracias a estos, es posible definir un macro que provoque que la expresión pasada sea evaluada en un literal compuesto con almacenamiento constante. Dado que un inicializador con almacenamiento constexpr debe ser una constante en tiempo de compilación, el compilador realizará la verificación necesaria. Lo más destacable es que la expresión mantiene su tipo y el valor resultante se corresponde exactamente con la expresión original. Sin embargo, esta técnica está limitada por la disponibilidad real de compiladores que soporten completamente C23. Por ejemplo, Clang en sus versiones actuales puede no aceptar ciertas combinaciones de almacenamiento para literales compuestos, mientras que GCC ya presenta una compatibilidad más sólida.
Por lo tanto, aunque prometedora, esta solución podría no ser práctica para proyectos que requieran un amplio soporte en compiladores y versiones más antiguas. En contextos donde el uso de extensiones GNU es aceptable, una herramienta poderosa es el operador __builtin_constant_p. Esta construcción especial devuelve verdadero cuando se evalúa una expresión constante en tiempo de compilación. Combinado con __builtin_choose_expr, que permite seleccionar cuál expresión compilar según una condición constante, es posible definir un macro que acepte una expresión y la valide, provocando un error de compilación si no se trata de una constante. Para ello, se declara una función ficticia con un atributo error que genera el fallo cuando es invocada, logrando que expresiones no constantes detengan la compilación sin silencios.
El beneficio adicional es que este enfoque mantiene intacto el tipo original, un aspecto valioso para evitar problemas de promoción o conversión de tipos. No obstante, al ser una extensión propietaria de GNU, este método puede no ser viable en ambientes estrictamente portables o cuando se pretende evitar dependencias de compiladores específicos. Una técnica que aprovecha características estándar de C11 es el uso de _Static_assert integrados dentro de estructuras anónimas combinadas con operaciones como sizeof para lograr la verificación del carácter constante. En este enfoque, la expresión que se quiere evaluar se coloca en la condición del static_assert como una expresión constante necesaria. La parte interesante y menos intuitiva es cómo se inserta static_assert dentro de un struct, algo permitido ya que static_assert es considerado una declaración y no produce miembros reales.
La macro genera entonces un valor que es la suma de la expresión que se quiere verificar y el resultado en tamaño de una estructura que contiene el static_assert, multiplicado por cero para no modificar el valor. Esta solución es portadora de ciertas limitaciones, especialmente la posibilidad de que el tipo de la expresión cambie debido a las reglas de promoción al realizar sumas con enteros, además de que la interpretación estricto del estándar espera expresiones constantes enteras en static_assert. Aunque compiladores populares han flexibilizado esto y permiten ciertas expresiones en punto flotante, algunas advertencias pueden emerger. Una variante que evita el uso de static_assert y puede ser utilizada ya desde C99 se basa en definir un arreglo con tamaño dado por la expresión en un literal compuesto. Este enfoque se fundamenta en que los literales compuestos no aceptan tipos de arrays de longitud variable para inicializadores, por lo cual pasar una expresión no constante que defina el tamaño del arreglo provocaría un error de compilación.
El truco es sumar a la expresión original cero veces el tamaño de este literal compuesto, generando una verificación silenciosa sin alterar el valor original. Al igual que la variante con static_assert, existen restricciones: las expresiones con punto flotante no son compatibles, y se deben considerar límites para el tamaño de arreglos, por ejemplo para evitar desbordamientos en 64 bits. El uso de constantes enumeradas (enum) para validar expresiones constantes es una opción antigua y compatible hasta con C89. Al declarar un valor enumerado con el entero resultante de la expresión, el compilador validará si esta es constante. Sin embargo, estos enum tienen limitaciones de scope y a menudo «filtran» su definición al entorno global o de la función, impidiendo múltiples usos o reutilizaciones en el mismo ámbito sin producir errores o warnings.
Además, esta técnica no admite expresiones en punto flotante. Para resolver esto parcialmente, una variante utiliza declarar el enum dentro de una función o una declaración de puntero a función, encerrando su scope, pero provoca habituales advertencias sobre enumeraciones anónimas difíciles de suprimir, lo que puede afectar la limpieza del código y la experiencia al compilar. Algunos de los métodos quirúrgicos con macros que emplean el operador coma permiten evitar ciertos cambios en el tipo de la expresión original, de forma que la verificación que utiliza sized operadores o static_assert se coloca en un operando izquierdo sin efecto, retornándose directamente el valor original. Si bien es eficiente para preservar el tipo, puede producir advertencias sobre expresiones sin efecto pero que pueden ser silenciadas mediante conversiones a void. Cabe resaltar que algunos enfoques menos comunes intentaban crear errores de compilación por tamaño negativo de arreglos en macros para forzar la verificación, pero la realidad es que compiladores como GCC tienden a emitir solo advertencias y no detienen la compilación, lo que hace estas soluciones poco fiables.
Por esta razón, métodos con atributo error en funciones falsas representan una apuesta más consistente para obligar la terminación cuando la expresión no es constante. En síntesis, las distintas estrategias para detectar si una expresión es constante en C varían en complejidad, portabilidad y robustez. La decisión de cuál utilizar depende no solo del estándar C usado, sino también del compilador y la necesidad específica del proyecto. Para código que busca la máxima compatibilidad, soluciones con enum o macros basados en sizeof son útiles aunque con limitaciones, mientras que para proyectos modernos y con acceso a extensiones GNU o C23, las técnicas con constexpr y __builtin_constant_p son más limpias y efectivas. Los desafíos no se limitan a lograr la verificación, sino también a mantener la expresión con su tipo original, evitar advertencias molestas y garantizar que la compilación falle cuando corresponde, sin falsas alarmas ni silencios peligrosos.