¿Cómo enviar entre afirmar () y static_assert (), dependnd si en contexto constexpr?

8

En las funciones constexpr de C ++ 11, una segunda declaración como una assert()no es posible. A static_assert()está bien, pero no funcionaría si la función se llama como función 'normal'. El operador de coma podría venir a ayudar a wrto. el assert(), pero es feo y algunas herramientas escupen advertencias al respecto.

Considere tal 'getter' que es perfectamente comprensible al lado de la afirmación. Pero me gustaría mantener algún tipo de afirmación para el tiempo de ejecución y el tiempo de compilación, pero no puedo sobrecargarlo dependiendo del contexto 'constexpr'.

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement( int idx ) const
  {
    ASSERT( idx < Size ); // a no-go for constexpr funcs in c++11
    // not possible, even in constexpr calls as being pointed out, but what I would like:
    static_assert( idx < Size, "out-of-bounds" );
    return m_vals[idx];
  }
};

Condiciones secundarias: C ++ 11, sin montón, sin excepciones, sin detalles del compilador.

Tenga en cuenta que los comentaristas señalaron (¡gracias!), static_assertSobre el argumento no es posible (pero sería bueno). El compilador me dio un error diferente en el acceso fuera de límites en esa situación.

Borph
fuente
" pero es feo " Bueno, el operador de coma puede ser feo, pero hace el trabajo en C ++ 11. La solución elegante sería cambiar a C ++ 14. :)
Bellota
" algunas herramientas escupen advertencias al respecto " ¿Qué herramientas y qué advertencias?
Bellota
El operador de coma solía ser mi solución, pero a) se advierte mediante análisis de código estático como qacpp (pautas de codificación) yb) estaba provocando un extraño error de sintaxis en un proyecto posterior (no entendí, sospecho que hay una macro de afirmación personalizada) . Bueno, ahora trato de evitarlo, pero estoy de acuerdo en que hizo el trabajo.
Borph
2
@Borph No, no puede usar static_assertdependiente idxen absoluto. Solo puede diagnosticar un valor incorrecto de idxsi la función se usa en un contexto que requiere una expresión constante, al forzar la evaluación de una construcción que la convierte en una expresión no constante. Fuera de dicho contexto, nunca puede verificar el valor en tiempo de compilación.
nogal

Respuestas:

2

Algo como

void assert_impl() { assert(false); } // Replace body with own implementation

#ifdef NDEBUG // Replace with own conditional
#define my_assert(condition) ((void)0)
#else
#define my_assert(condition) ((condition) ? (void()) : (assert_impl(), void()))
#endif

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement( int idx ) const
  {
    return my_assert(idx < Size), m_vals[idx];
  }
};

Dará un error en tiempo de compilación en caso de fallo de aserción si se usa en un contexto que requiere una expresión constante (porque llamará a una no constexprfunción).

De lo contrario, fallará en tiempo de ejecución con una llamada a assert(o su análogo).

Esto es lo mejor que puedes hacer hasta donde yo sé. No hay forma de usar el valor de idxforzar una verificación en tiempo de compilación fuera del contexto que requiere expresiones constantes.

La sintaxis del operador de coma no es agradable, pero las constexprfunciones de C ++ 11 son muy limitadas.

Por supuesto, como ya notó, el comportamiento indefinido se diagnosticará de todos modos si la función se usa en un contexto que requiere una expresión constante.

Si sabe que assert(o su análogo) no se expande a nada que esté prohibido en una expresión constante si la condición se evalúa truepero lo hace si se evalúa a false, entonces puede usarlo directamente en lugar de my_assertomitir la indirección que construyo en mi código

nuez
fuente
1
Dados los votos negativos, ¿podría alguien explicarme dónde está mi respuesta incorrecta?
nogal
Esta y la solución @ecatmur son similares, solo puedo elegir una respuesta. El tuyo es sencillo. Sin embargo, una observación: ¿ (void)0por qué en el NDEBUGcaso y void()en el otro? ¿O es realmente lo mismo?
Borph
@Borph (void)0es un no-op, que compila a nada (que es lo que quiere cuando NDEBUGse define), mientras que se necesita el void()fin de que el segundo y tercer operandos del operador condicional tienen el mismo tipo, void.
Bob__
@Borph, creo (void)0que también estaría bien en todos los casos. Acabo de reemplazarlo en el primer caso, porque void()también se puede analizar como tipo de función sin parámetros y sin tipo de retorno según el contexto. No se puede analizar de esa manera en las subexpresiones en el segundo caso.
nogal
3

Mejor que una expresión de coma, puede usar un condicional ternario. El primer operando es su predicado de aserción, el segundo operando es su expresión de éxito, y dado que el tercer operando puede ser cualquier expresión, incluso una no utilizable en un contexto constante de C ++ 11, puede usar una lambda para invocar las ASSERTinstalaciones de su biblioteca :

#define ASSERT_EXPR(pred, success)    \
    ((pred) ?                         \
     (success) :                      \
     [&]() -> decltype((success))     \
     {                                \
         ASSERT(false && (pred));     \
         struct nxg { nxg() {} } nxg; \
         return (success);            \
     }())

Explicación del cuerpo de la lambda:

  • ASSERT(false && (pred)) es para asegurarse de que su maquinaria de aserción se invoque con una expresión apropiada (para la stringificación).
  • struct nxg { nxg() {} } nxges para seguridad futura, para garantizar que si compila en C ++ 17 o superior con NDEBUGlambda todavía no constexprlo es, por lo que la afirmación se aplica en el contexto de evaluación constante.
  • return (success)está ahí por dos razones: para asegurarse de que el segundo y tercer operandos tengan el mismo tipo, y de modo que si su biblioteca respeta NDEBUGla successexpresión se devuelva independientemente pred. ( predse evaluará , pero es de esperar que los predicados de afirmación sean baratos de evaluar y no tengan efectos secundarios).

Ejemplo de uso:

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr int getElement( int idx ) const
  {
    return ASSERT_EXPR(idx < Size, m_vals[idx]);
  }
};

constexpr int I = Array<2>{1, 2}.getElement(1); // OK
constexpr int J = Array<2>{1, 2}.getElement(3); // fails
ecatmur
fuente
Ah! @walnut y una variable no inicializada podrían permitirse más adelante si no se accede a ella. Trataré de encontrar un mejor guardia. ¡Gracias!
ecatmur
sugerencia: s / [&]/ [&] -> decltype((success))para conservar referencias.
LF
@LF buen punto, gracias!
ecatmur
Tiene razón en que la predevaluación debería ser barata, pero no siempre es así. Entonces, como ASSERT_EXPRmacro general , no lo recomendaría. A veces hago llamadas costosas en una afirmación (por ejemplo, para verificar invariantes).
Borph
1
@Borph Supongo que incluso si activa NDEBUGpara deshabilitar las afirmaciones de tiempo de ejecución, aún desea que se verifiquen las afirmaciones en tiempo de compilación. Hacer que el cuerpo del caso de falla lambda no constexprsea ​​una forma de garantizar esto, pero tiene el costo de evaluar y descartar el predicado en tiempo de ejecución NDEBUG. De lo contrario, podría definir la macro en NDEBUGjusto return (success);.
ecatmur
2

static_assertNo se puede usar aquí. El argumento de una constexprfunción no está permitido en una expresión constante. Por lo tanto, no hay solución a su problema bajo las restricciones dadas.

Sin embargo, podemos resolver el problema doblando dos restricciones

  1. no usar static_assert(use otros métodos para producir un diagnóstico en tiempo de compilación), y

  2. no tenga en cuenta que el operador de coma "es feo y algunas herramientas escupen advertencias al respecto". (Mostrar su fealdad es una desafortunada consecuencia de los estrictos requisitos de las constexprfunciones de C ++ 11 )

Entonces, podemos usar un normal assert:

template <int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement(int idx) const
  {
    return assert(idx < Size), m_vals[idx];
  }
};

En un contexto de evaluación constante, esto emitirá un error de compilación como error: call to non-'constexpr' function 'void __assert_fail(const char*, const char*, unsigned int, const char*)'.

LF
fuente
-2

Esto es lo que funciona para mí para la mayoría de los compiladores: https://godbolt.org/z/4nT2ub

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement(int idx) const
  {
    assert(idx < Size);
    return m_vals[idx];
  }
};

Ahora static_assertes obsoleto ya constexprque no puede contener un comportamiento indefinido. Entonces, cuando buscas el compilador de índice de matriz informa el error adecuado. Vea aquí .

El problema es assert. Es una macro cuya implementación no está definida. Si el compilador utiliza una función que no es una constexpr, fallará, pero como puede ver, 3 compiladores principales no tienen ningún problema con eso.

Marek R
fuente
1
Esto no funciona en C ++ 11 (uno de los requisitos de OP). Los compiladores probablemente estén usando C ++ 14 o posterior por defecto.
nogal
con C++11esto falla solo en gcc godbolt.org/z/DB2zL3
Marek R
Mira los mensajes de advertencia. MSVC no acepta la /std:c++11bandera en absoluto y Clang permite el código, aunque requiere C ++ 14. Add -pedantic-errorsand Clang dará los errores adecuados que un compilador puro de C ++ 11 daría.
nogal
-3

c ++ 11 no puede ser tan ... idxconstante o no
sería bueno si tuviera una función cada una.
Podría ser algo así si se fuerza en una función

template<int Size>
struct Array {
    int m_vals[Size];
    constexpr const  int& getElement( int idx ) const       
    {
    if constexpr(is_same_v<decltype(idx), const int>)
        static_assert( idx < Size, "out-of-bounds" ); // a no-go for non-constexpr calls
    else 
        assert( idx < Size ); // a no-go for constexpr funcs in c++11

    return m_vals[idx];
    }
};

int main() {                // E.g. up to next //

        Array<7> M;  
        int i=M.getElement(1);
}                           //
nonock
fuente
1
No estoy seguro de si esto pretende ser una respuesta a la pregunta o no, pero su código no es válido en ninguna versión de C ++. El problema no es si es o no idxes const. Si esto es solo una propuesta estándar de C ++, entonces no veo cómo pertenece en la sección de respuestas.
nogal